Running Subprocesses with os/exec
Why os/exec?
Go programs often need to shell out to external tools: git, ffmpeg, openssl, database CLI utilities. The os/exec package runs subprocesses safely โ without a shell, without injection risks, and with full control over stdin/stdout/stderr.
Running a command and capturing output
Output() runs the command, waits for it to complete, and returns stdout as []byte. If the command exits with a non-zero status, err is *exec.ExitError.
CombinedOutput โ stdout + stderr together
Useful when you want the complete output regardless of which stream it came from.
Checking exit codes
Run() runs the command and waits โ use it when you don't need the output.
Connecting stdin/stdout/stderr
Assign io.Reader/io.Writer values to cmd.Stdin, cmd.Stdout, cmd.Stderr before calling Run().
Streaming output with pipes
Use StdoutPipe() + cmd.Start() + cmd.Wait() when you need to process output line by line while the command runs.
Security: never use shell=true
Do not construct commands by concatenating user input into a shell string:
exec.Command takes the program name and each argument separately. No shell is invoked โ special characters in arguments are passed literally.
Setting environment and working directory
cmd.Dir sets the working directory. cmd.Env replaces the entire environment โ start from os.Environ() to inherit the current env, then append overrides.
Context cancellation
exec.CommandContext kills the subprocess when the context is cancelled โ essential for server-side use where runaway processes must not outlive their request.
Knowledge Check
Why is exec.Command("sh", "-c", "ls "+userInput) dangerous?
What is the difference between cmd.Output() and cmd.Run()?
When should you use exec.CommandContext instead of exec.Command?