109
votes

I'm writing a program in Go that executes a server like program (also Go). Now I want to have the stdout of the child program in my terminal window where I started the parent program. One way to do this is with the cmd.Output() function, but this prints the stdout only after the process has exited. (That's a problem because this server-like program runs for a long time and I want to read the log output)

The variable out is of type io.ReadCloser and I don't know what I should do with it to achieve my task, and I can't find anything helpful on the web on this topic.

func main() {
    cmd := exec.Command("/path/to/my/child/program")
    out, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Println(err)
    }
    err = cmd.Start()
    if err != nil {
        fmt.Println(err)
    }
    //fmt.Println(out)
    cmd.Wait()
} 

Explanation to the code: uncomment the Println function to get the code to compile, I know that Println(out io.ReadCloser) is not a meaningful function.
(it produces the output &{3 |0 <nil> 0} ) These two lines are just required to get the code to compile.

3
Your "exec" line of the import statement should be "os/exec".John Leimon
thanks for the info, actually it was only exec pre go1, now its in os. updated it for go1mbert
I don't think that you actually need to call io.Copy within go routinesrmonjo
I don't think you need to call cmd.Wait() or the for{} loop... why are these here?weberc2
@weberc2 for this look down to elimisteve's answer. The for loop is not needed if you just want to run the program once. But if you don't call cmd.Wait(), your main() may end before your called program finishes, and you don't get the output you wantmbert

3 Answers

214
votes

Now I want to have the stdout of the child program in my terminal window where I started the parent program.

No need to mess with pipes or goroutines, this one is easy.

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
}
20
votes

I believe that if you import io and os and replace this:

//fmt.Println(out)

with this:

go io.Copy(os.Stdout, out)

(see documentation for io.Copy and for os.Stdout), it will do what you want. (Disclaimer: not tested.)

By the way, you'll probably want to capture standard-error as well, by using the same approach as for standard-output, but with cmd.StderrPipe and os.Stderr.

16
votes

For those who don't need this in a loop, but would like the command output to echo into the terminal without having cmd.Wait() blocking other statements:

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
)

func checkError(err error) {
    if err != nil {
        log.Fatalf("Error: %s", err)
    }
}

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")

    // Create stdout, stderr streams of type io.Reader
    stdout, err := cmd.StdoutPipe()
    checkError(err)
    stderr, err := cmd.StderrPipe()
    checkError(err)

    // Start command
    err = cmd.Start()
    checkError(err)

    // Don't let main() exit before our command has finished running
    defer cmd.Wait()  // Doesn't block

    // Non-blockingly echo command output to terminal
    go io.Copy(os.Stdout, stdout)
    go io.Copy(os.Stderr, stderr)

    // I love Go's trivial concurrency :-D
    fmt.Printf("Do other stuff here! No need to wait.\n\n")
}