I'm working with os/exec, sending input and receiving output as a command runs.
I need to store the command's return code when it finishes, so I have a goroutine with err := cmd.Wait()
, and I get any failure return code from the err.
But Wait() seems to throw away the remaining stdout which I need also.
So how do I preserve the remaining stdout of a os/exec.Cmd after Cmd.Wait()?
Example code, using the Unix bc calculator command:
package main
import (
"fmt"
"os/exec"
"bufio"
"io"
"time"
)
func main() {
cmd := exec.Command("sh", "-c", "bc")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()
go func() {
cmd.Wait()
fmt.Println("finished")
}()
io.WriteString(stdin, "1 2\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "3 4\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "5 6\n")
io.WriteString(stdin, "quit\n") // cmd.Wait() runs
time.Sleep(time.Second)
// Prints false :(
fmt.Println(scanner.Scan(), scanner.Text())
}
This prints: true 3 true 7 finished false
I'd like: true 3 true 7 finished true 11
I also tried setting cmd.Stdout to a bytes.Buffer like:
var buf bytes.Buffer
cmd.Stdout = &buf
scanner := bufio.NewScanner(&buf)
But that was unreliable. It printed all false unless I added in delays with time.Sleep().
CodePudding user response:
Call cmd.Wait() after reading to the end of stdout.
Option 1: call cmd.Wait from the main goroutine after scanner.Scan() returns false.
cmd := exec.Command("sh", "-c", "bc")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()
io.WriteString(stdin, "1 2\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "3 4\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "5 6\n")
io.WriteString(stdin, "quit\n") // cmd.Wait() runs
fmt.Println(scanner.Scan(), scanner.Text())
fmt.Println(scanner.Scan(), scanner.Text()) // prints false
cmd.Wait()
Option 2: read from the waiting goroutine:
cmd := exec.Command("sh", "-c", "bc")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()
var wg sync.WaitGroup
wg.Add(1)
go func() {
for scanner.Scan() {
fmt.Println(scanner.Text())
}
cmd.Wait()
defer wg.Done()
}()
io.WriteString(stdin, "1 2\n")
io.WriteString(stdin, "3 4\n")
io.WriteString(stdin, "5 6\n")
io.WriteString(stdin, "quit\n") // cmd.Wait() runs
wg.Wait()