Skip to content
shellhacks.dev
Go

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.

2 min read
Summarize in AI

A Go program should run something like printf piped into sort without going through a shell.

Go
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"))
}
Text
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.

Share

More snippets

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.