Joe
Joe

Reputation: 856

Streaming exec.Command StdoutPipe

I'm trying to stream the Stdout of a shell command to the console, but am having difficulty.

Here's what I currently have:

cmd := exec.Command("sh", "-c", `for number in {0..10}; do echo "$number "; done;`)
pipe, _ := cmd.StdoutPipe()
reader := bufio.NewReader(pipe)
line, err := reader.ReadString('\n')
for err == nil {
    fmt.Println(line)
    line, err = reader.ReadString('\n')
}

I would expect this to print out the numbers 0 through 10, but it seems to hang on line 3 (the first call to ReadString.

I started with cmd.Output() and cmd.CombinedOutput(), but those methods seem to buffer the entire output stream until the command is complete. I need to process the output as it streams, not wait until the command is complete.

I also tried this: continuously reading from exec.Cmd output, but it didn't seem to work and I went away from it because I really want to read lines and not have to manage the buffer manually.

Other things I've looked through:

Upvotes: 12

Views: 9495

Answers (2)

Christopher Scott
Christopher Scott

Reputation: 3156

This is a slight variation on the answer from cerise-limón, but I needed the output to stream to the console as it appears. In addition, my command never produced an EOF.

Wrapping the buffer read in a go routine allows it to continue pumping output to the console while cmd.Wait() waits for the command to complete. Since Wait closes the StdoutPipe, it will cause reader.ReadString to error once the command is complete, exiting the routine.

cmd := exec.Command(someCommand, someArg)
pipe, _ := cmd.StdoutPipe()
if err := cmd.Start(); err != nil {
    // handle error
}
go func(p io.ReadCloser) {
    reader := bufio.NewReader(pipe)
    line, err := reader.ReadString('\n')
    for err == nil {
        fmt.Println(line)
        line, err = reader.ReadString('\n')
    }
}(pipe)

if err := cmd.Wait(); err != nil {
    // handle error
}

Upvotes: 1

Thundercat
Thundercat

Reputation: 121129

You need to start the command:

cmd := exec.Command("sh", "-c", `for number in {0..10}; do echo "$number "; done;`)
pipe, _ := cmd.StdoutPipe()
if err := cmd.Start(); err != nil {
   // handle error
}
reader := bufio.NewReader(pipe)
line, err := reader.ReadString('\n')
for err == nil {
    fmt.Println(line)
    line, err = reader.ReadString('\n')
}

Call Wait after reaching EOF.

The Output and CombinedOutput methods worked for you because these methods call Start internally.

Upvotes: 18

Related Questions