Skip to content

Commit bf3307a

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 bf3307a

2 files changed

Lines changed: 64 additions & 24 deletions

File tree

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: 40 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 }
@@ -259,6 +261,15 @@ func (s *String) SetRunning() {
259261
// wait on the background command
260262
// to be done.
261263
func (s *String) Wait() {
264+
// Read all 'unread bytes' from stdout/stderr but just in case it hasn't been read yet
265+
if s.Scanner != nil {
266+
for s.Scanner.Scan() {
267+
s.StdoutStderr.Write(s.Scanner.Bytes())
268+
}
269+
270+
s.Value = strings.TrimSpace(s.StdoutStderr.String())
271+
}
272+
262273
s.mustHaveMutex()
263274
s.mux.Lock()
264275
s.mux.Unlock()
@@ -271,9 +282,8 @@ func (s *String) Kill() error {
271282

272283
// The command value includes output and possible error
273284
// 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)
285+
output := s.StdoutStderr.String()
286+
s.Value = strings.TrimSpace(output)
277287

278288
if err != nil {
279289
return err
@@ -291,19 +301,32 @@ func (s *String) Kill() error {
291301
// - str.done
292302
func (s *String) SetCmdResult(Ok *Boolean) {
293303
s.Ok = Ok
294-
var output string
295304

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

302307
// trim space at both ends of out.String(); works in both linux and windows
303308
s.Value = strings.TrimSpace(output)
304309
s.Done = TRUE
305310
}
306311

312+
func (s *String) Next() (Object, Object) {
313+
if s.Scanner == nil {
314+
return nil, nil
315+
}
316+
317+
for s.Scanner.Scan() {
318+
line := s.Scanner.Text()
319+
s.lineno += 1
320+
return &Number{Value: float64(s.lineno - 1)}, &String{Value: line, Scanner: s.Scanner, lineno: s.lineno}
321+
}
322+
323+
return nil, nil
324+
}
325+
326+
func (s *String) Reset() {
327+
s.lineno = 0
328+
}
329+
307330
type Builtin struct {
308331
Token token.Token
309332
Fn BuiltinFunction

0 commit comments

Comments
 (0)