Skip to content

Commit 9f3ec9b

Browse files
Ability to read command output line by line in a loop
NOTE: the command need to be executed in the background. ``` for line in `tail -f /tmp/log &` { echo(line) } ``` Also combines the stdout and stderr of executed command in the same ouput, like native Go implementation of `cmd.CombinedOutput`.
1 parent 5de8d01 commit 9f3ec9b

File tree

2 files changed

+55
-24
lines changed

2 files changed

+55
-24
lines changed

evaluator/evaluator.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package evaluator
22

33
import (
4+
"bufio"
45
"bytes"
56
"fmt"
7+
"io"
68
"io/ioutil"
79
"math"
810
"os"
@@ -1526,13 +1528,9 @@ func evalCommandExpression(tok token.Token, cmd string, env *object.Environment)
15261528
c := exec.Command(parts[0], append(parts[1:], cmd)...)
15271529
c.Env = os.Environ()
15281530
c.Stdin = os.Stdin
1529-
var stdout bytes.Buffer
1530-
var stderr bytes.Buffer
1531-
c.Stdout = &stdout
1532-
c.Stderr = &stderr
1531+
var stdoutStderr bytes.Buffer
15331532

1534-
s.Stdout = &stdout
1535-
s.Stderr = &stderr
1533+
s.StdoutStderr = &stdoutStderr
15361534
s.Cmd = c
15371535
s.Token = tok
15381536

@@ -1543,14 +1541,33 @@ func evalCommandExpression(tok token.Token, cmd string, env *object.Environment)
15431541
// wait for it by calling s.Wait().
15441542
s.SetRunning()
15451543

1546-
err := c.Start()
1544+
stdoutPipe, err := s.Cmd.StdoutPipe()
15471545
if err != nil {
15481546
s.SetCmdResult(FALSE)
15491547
return FALSE
15501548
}
15511549

1550+
stderrPipe, err := s.Cmd.StderrPipe()
1551+
if err != nil {
1552+
s.SetCmdResult(FALSE)
1553+
return FALSE
1554+
}
1555+
1556+
combinedReader := io.MultiReader(stdoutPipe, stderrPipe)
1557+
1558+
s.Scanner = bufio.NewScanner(combinedReader)
1559+
s.Scanner.Split(bufio.ScanLines)
1560+
1561+
if err := s.Cmd.Start(); err != nil {
1562+
s.SetCmdResult(FALSE)
1563+
return FALSE
1564+
}
1565+
15521566
go evalCommandInBackground(s)
15531567
} else {
1568+
c.Stdout = &stdoutStderr
1569+
c.Stderr = &stdoutStderr
1570+
15541571
err = c.Run()
15551572
}
15561573

object/object.go

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package object
22

33
import (
4+
"bufio"
45
"bytes"
56
"fmt"
67
"os/exec"
@@ -211,14 +212,15 @@ func (f *Function) Json() string { return f.Inspect() }
211212
// cmd.wait() // ...
212213
// cmd.done // TRUE
213214
type String struct {
214-
Token token.Token
215-
Value string
216-
Ok *Boolean // A special property to check whether a command exited correctly
217-
Cmd *exec.Cmd // A special property to access the underlying command
218-
Stdout *bytes.Buffer
219-
Stderr *bytes.Buffer
220-
Done *Boolean
221-
mux *sync.Mutex
215+
Token token.Token
216+
Value string
217+
Ok *Boolean // A special property to check whether a command exited correctly
218+
Cmd *exec.Cmd // A special property to access the underlying command
219+
StdoutStderr *bytes.Buffer
220+
Scanner *bufio.Scanner
221+
Done *Boolean
222+
lineno int64
223+
mux *sync.Mutex
222224
}
223225

224226
func (s *String) Type() ObjectType { return STRING_OBJ }
@@ -271,9 +273,8 @@ func (s *String) Kill() error {
271273

272274
// The command value includes output and possible error
273275
// We might want to change this
274-
output := s.Stdout.String()
275-
outputErr := s.Stderr.String()
276-
s.Value = strings.TrimSpace(output) + strings.TrimSpace(outputErr)
276+
output := s.StdoutStderr.String()
277+
s.Value = strings.TrimSpace(output)
277278

278279
if err != nil {
279280
return err
@@ -291,19 +292,32 @@ func (s *String) Kill() error {
291292
// - str.done
292293
func (s *String) SetCmdResult(Ok *Boolean) {
293294
s.Ok = Ok
294-
var output string
295295

296-
if Ok.Value {
297-
output = s.Stdout.String()
298-
} else {
299-
output = s.Stderr.String()
300-
}
296+
output := s.StdoutStderr.String()
301297

302298
// trim space at both ends of out.String(); works in both linux and windows
303299
s.Value = strings.TrimSpace(output)
304300
s.Done = TRUE
305301
}
306302

303+
func (s *String) Next() (Object, Object) {
304+
if s.Scanner == nil {
305+
return nil, nil
306+
}
307+
308+
for s.Scanner.Scan() {
309+
line := s.Scanner.Text()
310+
s.lineno += 1
311+
return &Number{Value: float64(s.lineno - 1)}, &String{Value: line, Scanner: s.Scanner, lineno: s.lineno}
312+
}
313+
314+
return nil, nil
315+
}
316+
317+
func (s *String) Reset() {
318+
s.lineno = 0
319+
}
320+
307321
type Builtin struct {
308322
Token token.Token
309323
Fn BuiltinFunction

0 commit comments

Comments
 (0)