Redirect stdout pipe of child process in Go

51,203

Solution 1

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()
}

Solution 2

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.

Solution 3

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")
}
Share:
51,203

Related videos on Youtube

mbert
Author by

mbert

Updated on October 13, 2020

Comments

  • mbert
    mbert over 3 years

    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.

    • John Leimon
      John Leimon about 11 years
      Your "exec" line of the import statement should be "os/exec".
    • mbert
      mbert about 11 years
      thanks for the info, actually it was only exec pre go1, now its in os. updated it for go1
    • rmonjo
      rmonjo almost 11 years
      I don't think that you actually need to call io.Copy within go routines
    • weberc2
      weberc2 over 10 years
      I don't think you need to call cmd.Wait() or the for{} loop... why are these here?
    • mbert
      mbert over 10 years
      @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 want
    • weberc2
      weberc2 over 10 years
      @mbert I don't think that's true. Consider this example in which "helper" is a program w/ a loop that prints a number every second. io.Copy() will block until the program finishes. play.golang.org/p/_I1MxATn90 Of course, @elimisteve's answer is more concise and generally better, but I still didn't need a for loop, goroutines, or cmd.Wait()
    • mbert
      mbert over 10 years
      I just checked, io.Copy will block, but running go routines do not block main(). So after your main is finished it will take down your child program and copying goroutines. In this case cmd.Wait is required. (updated the solution to a shorter code)
    • AJcodez
      AJcodez about 10 years
      @mbert you can throw away errors for quickly testing something with out, _ := cmd...
    • kioopi
      kioopi almost 10 years
      If you try to open vim this way and your terminal output is messed up afterwards: Add a line: cmd.Stdin = os.Stdin before run.
  • ruakh
    ruakh over 12 years
    @mbert: I had used enough other languages, and had read enough about Go, to have a hunch for what feature would likely exist to do this, and in approximately what form; then I just had to look through the relevant package-docs (found by Googling) to confirm that my hunch was correct, and to find the necessary details. The hardest parts were (1) finding what standard-output is called (os.Stdout) and (2) confirming the premise that, if you don't call cmd.StdoutPipe() at all, the standard-output goes to /dev/null rather than to the parent-process's standard-output.
  • galaktor
    galaktor over 10 years
    Minor fyi: (Obviously) you might miss results of the goroutines started if your "do other stuff here" completes faster than the goroutines. The main() exiting will cause the goroutines to end as well. so you could potentially not end up actually outputing to echo in the terminal if you don't wait for the cmd to finish.
  • Nucleon
    Nucleon about 10 years
    In addition, if you want the command to listen for input you can simply set cmd.Stdin = os.Stdin thereby making it as if you had literally executed that command from your shell.
  • Rick Smith
    Rick Smith almost 10 years
    For those looking to redirect to log instead of stdout, there is an answer here