Spawning Processes
errors fmt io os/exec stringsSometimes our Go programs need to spawn other processes. The os/exec package runs external commands, wires up their input and output, and reports how they exited.
package main
import (
"errors"
"fmt"
"io"
"os/exec"
"strings"
)
func main() {We'll start with a simple command that takes no arguments or input and just prints something to stdout. The exec.Command helper creates an object to represent this external process.
dateCmd := exec.Command("echo", "hello from echo")The Output method runs the command, waits for it to finish and collects its standard output. If there were no errors, dateOut will hold bytes with the output.
dateOut, err := dateCmd.Output()
if err != nil {
panic(err)
}
fmt.Print("> echo\n", string(dateOut))Output and other methods of Command will return *exec.Error if there was a problem executing the command (e.g. wrong path), and *exec.ExitError if the command ran but exited with a non-zero return code.
_, err = exec.Command("ls", "/a/nonexistent/path").Output()
var execErr *exec.Error
var exitErr *exec.ExitError
switch {
case errors.As(err, &execErr):
fmt.Println("failed executing:", err)
case errors.As(err, &exitErr):The exact code differs between systems, so we only check it is non-zero.
fmt.Println("command failed with a non-zero exit code:", exitErr.ExitCode() != 0)
default:
panic(err)
}LookPath reports whether a program can be found on the PATH.
_, err = exec.LookPath("no-such-program-xyz")
fmt.Println("lookpath:", errors.Is(err, exec.ErrNotFound))Next we'll look at a slightly more involved case where we pipe data to the external process on its stdin and collect the results from its stdout.
grepCmd := exec.Command("grep", "hello")Here we explicitly grab input/output pipes, start the process, write some input to it, read the resulting output, and finally wait for the process to exit.
grepIn, _ := grepCmd.StdinPipe()
grepOut, _ := grepCmd.StdoutPipe()
grepCmd.Start()
grepIn.Write([]byte("hello grep\ngoodbye grep"))
grepIn.Close()
grepBytes, _ := io.ReadAll(grepOut)
grepCmd.Wait()We omitted error checks in the above example, but you could use the usual if err != nil pattern for all of them. We also only collect the StdoutPipe results, but you could collect the StderrPipe in exactly the same way.
fmt.Println("> grep hello")
fmt.Print(string(grepBytes))Note that when spawning commands we need to provide an explicitly delineated command and argument array, vs. being able to just pass in one command-line string. If you want to spawn a full command with a string, you can use bash's -c option.
lsCmd := exec.Command("bash", "-c", "echo one two three | wc -w")
lsOut, err := lsCmd.Output()
if err != nil {
panic(err)
}
fmt.Println("> wc -w")
fmt.Println(strings.TrimSpace(string(lsOut)))Stdin can also be any io.Reader, and CombinedOutput collects stdout and stderr together.
sortCmd := exec.Command("sort")
sortCmd.Stdin = strings.NewReader("pear\napple\nfig\n")
sorted, _ := sortCmd.CombinedOutput()
fmt.Print("> sort\n", string(sorted))
}$ go run ./85_spawning_processes > echo hello from echo command failed with a non-zero exit code: true lookpath: true > grep hello hello grep > wc -w 3 > sort apple fig pear