Pipe Two Commands Together in Go with os/exec
Connect two commands in Go by passing the first one's StdoutPipe to the second one's Stdin, start both, then wait on both. A pipe has no pipefail, so check each error.
A Go program should run something like printf piped into sort without going through a shell.
package main
import (
"bytes"
"fmt"
"os/exec"
)
func run(first, second *exec.Cmd) {
pipe, err := first.StdoutPipe()
if err != nil {
panic(err)
}
second.Stdin = pipe
var out bytes.Buffer
second.Stdout = &out
if err := first.Start(); err != nil {
panic(err)
}
if err := second.Start(); err != nil {
panic(err)
}
e1 := first.Wait()
e2 := second.Wait()
fmt.Printf("output %q, first: %v, second: %v\n", out.String(), e1, e2)
}
func main() {
run(exec.Command("printf", "pear\napple\nfig\n"), exec.Command("sort"))
run(exec.Command("sh", "-c", "echo lost; exit 3"), exec.Command("cat"))
}output "apple\nfig\npear\n", first: <nil>, second: <nil>
output "lost\n", first: exit status 3, second: <nil>StdoutPipe returns a reader for the first command's output, and assigning it to the second command's Stdin joins them. Both commands are started before either is waited on, and then each Wait returns its own error. The first call printed apple fig pear in order with both errors nil. The second shows what a shell hides: the producer exited with exit status 3, yet the consumer succeeded and returned lost, so the pipeline needs both errors checked. See Cmd.StdoutPipe.
Reading from the pipe yourself before the command is done and then calling Wait first can lose output, as the package notes. When the second command is only a filter, sh -c 'a | b' with set -o pipefail is shorter but brings the shell back. This was run with Go 1.19.