os/exec

Guided tour · CLI & Runtime · pkg.go.dev →

Run external commands. Connect their stdin/stdout to pipes or buffers, pass env and context, capture output.

Simple runs

Output — run and capture stdout

out, err := exec.Command("git", "rev-parse", "HEAD").Output()
if err != nil { log.Fatal(err) }
fmt.Println(strings.TrimSpace(string(out)))

CombinedOutput — stdout + stderr together

Good for 'just show me what happened' error reporting.

out, err := exec.Command("go", "test", "./...").CombinedOutput()
if err != nil {
    fmt.Fprintln(os.Stderr, string(out))
    os.Exit(1)
}

Run — don't capture, just wait for exit

cmd := exec.Command("make", "build")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil { log.Fatal(err) }

Context and timeouts

CommandContext — kill on cancel

Signals go to the child when ctx is canceled. Use this instead of spawning and forgetting.

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "curl", url)
if err := cmd.Run(); err != nil { log.Fatal(err) }

Streaming I/O

StdinPipe and StdoutPipe

cmd := exec.Command("sort")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()

cmd.Start()
go func() {
    defer stdin.Close()
    fmt.Fprintln(stdin, "c\nb\na")
}()
b, _ := io.ReadAll(stdout)
cmd.Wait()
fmt.Print(string(b))

Environment and working directory

Custom env and cwd

cmd := exec.Command("bin/tool")
cmd.Dir = "/srv/app"
cmd.Env = append(os.Environ(), "FOO=bar")

LookPath — resolve a command in PATH

bin, err := exec.LookPath("git")
if err != nil { log.Fatal("git not installed") }
fmt.Println(bin)

Detecting exit codes

ExitError

if err := cmd.Run(); err != nil {
    var ee *exec.ExitError
    if errors.As(err, &ee) {
        fmt.Println("exited with", ee.ExitCode())
    }
}