A Quick Guide to Go's os/exec Package: Running External Commands and Real‑World Use Cases
This article introduces Go's built‑in os/exec package, explains its exported types and methods, demonstrates how to run commands synchronously or in the background, capture output, handle pipes, environment variables, working directories, and shows a practical case study using the tempredis library to start a Redis server programmatically.
The Go os/exec package provides a simple way to start external programs such as redis-server , ls , or any shell command, offering functions like Command , CommandContext , Run , Start , Output , and CombinedOutput .
Typical usage starts with creating a command object, for example: cmd := exec.Command("echo", "Hello, World!") err := cmd.Run() which runs the command and waits for it to finish, logging any error.
Running a command in the background uses Start followed by Wait or by reading from pipes. The Run method is equivalent to Start + Wait :
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}Context‑aware execution is possible with exec.CommandContext , allowing automatic cancellation on timeout:
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
cmd := exec.CommandContext(ctx, "sleep", "5")
if err := cmd.Run(); err != nil {
log.Fatalf("Command failed: %v", err)
}To capture output, use Output (stdout only) or CombinedOutput (stdout + stderr):
cmd := exec.Command("echo", "Hello, World!")
output, err := cmd.Output()
fmt.Println(string(output))Standard streams can be redirected by assigning to cmd.Stdout and cmd.Stderr , or by using StdoutPipe , StderrPipe , and StdinPipe for custom handling, such as feeding data via an io.Reader :
cmd := exec.Command("grep", "hello")
cmd.Stdin = bytes.NewBufferString("hello world\nhi there\n")
output, _ := cmd.Output()
fmt.Println(string(output))Environment variables are accessed with cmd.Environ() and set via the Env slice:
cmd := exec.Command("printenv", "ENV_VAR")
cmd.Env = append(cmd.Env, "ENV_VAR=HelloWorld")
output, _ := cmd.Output()
fmt.Println(string(output))Pipes can be built manually using StdoutPipe and assigning the resulting reader to another command's Stdin , or by invoking a shell with bash -c to use shell operators like | :
cmd := exec.Command("bash", "-c", "echo 'hello world' | grep hello")
output, _ := cmd.Output()
fmt.Println(string(output))The working directory is set with cmd.Dir , and exit status can be inspected via exec.ExitError after a failed Run call.
Finding an executable in the system PATH is done with exec.LookPath :
path, err := exec.LookPath("ls")
fmt.Printf("ls is available at %s\n", path)A practical example is the third‑party tempredis library, which uses os/exec to start a temporary Redis server. The library defines a Config map, a Server struct that holds an *exec.Cmd , and methods such as Start , start , ready , waitFor , Stdout , Stderr , Term , and Kill . It launches redis-server - , writes configuration via the command's stdin, and monitors stdout for readiness strings like "The server is now ready to accept connections".
In summary, os/exec is a versatile tool for process management in Go, suitable for backend services, testing, and automation tasks, and its patterns are demonstrated both in simple snippets and in a real‑world library that starts Redis instances on demand.
Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.