Running shell command and capturing the output
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Running a shell command from code is easy; doing it safely and predictably takes more care. The main design questions are whether you really need a shell, how you want to capture standard output and standard error, and how you will avoid command-injection bugs when arguments come from outside your program.
Prefer Process APIs Over Raw Shell Strings
If all you need is to run a program and read its output, use your language's process API directly instead of feeding a single string into a shell. That avoids quoting problems and reduces injection risk.
In Python, subprocess.run is the usual answer:
This launches echo directly, captures the output as text, and raises an exception if the command fails.
Capture stdout and stderr Separately
Most process APIs expose both output streams. Keep them separate unless you have a strong reason to merge them.
This separation matters when a command writes normal data to standard output but writes diagnostics to standard error.
Use the Shell Only When You Need Shell Features
A shell is useful for pipelines, wildcard expansion, redirection, and compound expressions. If you need those features, then use the shell consciously.
That works, but shell=True should be the exception, not the default. If untrusted input is interpolated into that string, the risk becomes immediate.
Timeouts and Long-Running Commands
Real programs also need control over hanging processes. Use a timeout so a stalled child process does not block your program forever.
This is one of the easiest reliability wins when wrapping external commands.
A Reusable Helper
If you run commands often, wrap the behavior you want in one helper function.
A helper keeps output capture, error handling, and timeouts consistent across the codebase.
If the command can produce a lot of output, think about whether you want to buffer everything in memory. subprocess.run is convenient for short output, but long-running tools are sometimes better handled with Popen so you can stream lines incrementally instead of waiting for the process to finish.
Common Pitfalls
The biggest mistake is building shell strings from user input. If you do that with shell=True, you are inviting command injection.
Another mistake is reading only standard output and ignoring standard error and the return code. Many failures become invisible that way.
A third mistake is using shell execution for tasks that should use a native library. If you need to list files, parse JSON, or move data within the same program, a library call is usually safer and faster than spawning a shell.
Summary
- Use your language's process API to run commands and capture output safely.
- Prefer argument lists over raw shell strings whenever possible.
- Capture
stdout,stderr, and the exit code explicitly. - Use
shell=Trueonly when you need actual shell features such as pipes or redirection. - Add timeouts and avoid interpolating untrusted input into commands.

