Question
How can I write a Python function that executes a shell command and returns all of its output as a string, whether the command succeeds or fails?
For example, I want to run a command such as:
run_command("mysqladmin create test -uroot -pmysqladmin12")
If the database already exists, the function should return the message normally displayed in the terminal, such as:
mysqladmin: CREATE DATABASE failed; error: 'Can't create database 'test'; database exists'
Short Answer
You will learn how terminal programs use standard output, standard error, and exit codes, then use Python's subprocess module to run commands and capture their combined text output.
Concept
Command-line programs communicate through three important channels:
- Standard output (
stdout): normal results, such as a list of files or JSON data. - Standard error (
stderr): diagnostics, warnings, and error messages. - Exit code: a numeric status returned when the command finishes.
0usually means success; a non-zero value usually means failure.
A common surprise is that an error message is often written to stderr, not stdout. Therefore, capturing only stdout can make it appear that a failed command returned nothing.
Python's built-in subprocess module starts external programs. To collect output similar to what you see in a terminal, redirect stderr into stdout with stderr=subprocess.STDOUT. The resulting text contains both normal output and error output.
This matters when writing automation scripts, developer tools, deployment utilities, test runners, and integrations with existing command-line programs.
Mental Model
Think of a command as a worker sending you two streams of notes:
- One results tray for ordinary information (
stdout). - One problem tray for warnings and failures (
stderr).
If you read only the results tray, you may miss the worker's explanation of what went wrong. Combining the trays lets your function return all visible messages in one string.
The exit code is the worker's final status card: 0 means the job completed successfully, while another number signals that something failed or needs attention.
Syntax and Examples
Use subprocess.run() in modern Python. Pass command arguments as a list whenever possible.
import subprocess
def run_command(args):
completed = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=False,
)
return completed.stdout
output = run_command(["python", "--version"])
print(output)
stdout=subprocess.PIPE tells Python to collect normal output instead of immediately printing it.
stderr=subprocess.STDOUT merges error output into the same collected output.
text=True decodes the bytes produced by the program into a Python string.
check=False means that a non-zero exit code does not raise an exception. This is useful when you want to return an error message as normal function output.
To also keep the exit status, return both values:
import subprocess
def run_command(args):
completed = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=,
check=,
)
completed.returncode, completed.stdout
code, output = run_command([, , , , ])
()
(output)
Step by Step Execution
Consider this command, which writes an error message because the requested file does not exist:
import subprocess
completed = subprocess.run(
["python", "-c", "import sys; sys.stderr.write('File was not found\\n'); sys.exit(1)"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=False,
)
print(completed.returncode)
print(completed.stdout)
Execution flow:
subprocess.run()starts a separate Python process.- That process writes
File was not foundtostderr. stderr=subprocess.STDOUTredirects that text into the collected output stream.- The process exits with code
1. - Because
check=False, Python returns aCompletedProcessobject instead of raisingCalledProcessError. completed.returncodeis1.completed.stdoutis the string"File was not found\n".
Real World Use Cases
- Database administration: run migration or database creation commands and record any server error text.
- Build automation: execute tools such as
npm,pytest,make, or a compiler and display their logs when a build fails. - Deployment scripts: run Git, Docker, or cloud CLI commands and store their output in deployment logs.
- Health checks: call a network utility or service CLI, then inspect the return code and output.
- Data pipelines: launch a conversion program and save diagnostics when malformed input causes a failure.
- Developer tooling: wrap a command-line formatter, linter, or test runner in a Python application.
Real Codebase Usage
In production code, developers usually need more than a raw output string.
Return output and status
A command can fail while still producing useful diagnostics. Keep the return code alongside the output:
from dataclasses import dataclass
import subprocess
@dataclass
class CommandResult:
returncode: int
output: str
def run_command(args: list[str]) -> CommandResult:
completed = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=False,
)
return CommandResult(completed.returncode, completed.stdout)
Use a guard clause for failures
result = run_command(["git", "status", "--short"])
if result.returncode != 0:
raise RuntimeError(f"git status failed:\n{result.output}")
print(result.output)
Add a timeout
External processes can hang. Set a timeout for commands that should finish quickly:
Common Mistakes
Capturing only stdout
This often misses error messages:
# Incomplete for commands that write errors to stderr.
completed = subprocess.run(
["some-command"],
stdout=subprocess.PIPE,
text=True,
)
Fix it by combining streams:
stderr=subprocess.STDOUT
Using check=True when failures are expected
# Raises subprocess.CalledProcessError for non-zero exit codes.
subprocess.run(["some-command"], check=True)
Use check=False when your function should return the command's error output. Use check=True when failure should immediately stop the current operation.
Treating output text as proof of success
A command may print text and still fail. Always inspect returncode when success matters:
if completed.returncode != 0:
print("The command failed")
Using with untrusted input
Comparisons
| Approach | Captures stdout | Captures stderr | Raises on non-zero exit | Best use |
|---|---|---|---|---|
subprocess.run(args) | No | No | No | Let output go directly to the terminal |
run(..., capture_output=True, text=True) | Yes | Yes, separately | No | You need stdout and stderr as separate strings |
run(..., stdout=PIPE, stderr=STDOUT, text=True) | Yes | Yes, combined | No | Return terminal-like output as one string |
Cheat Sheet
import subprocess
completed = subprocess.run(
["program", "argument"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
check=False,
)
output = completed.stdout
exit_code = completed.returncode
stdout: ordinary command output.stderr: error and diagnostic output.stderr=subprocess.STDOUT: combine both streams.text=True: return strings rather than bytes.returncode == 0: usual success convention.check=False: return normally even if the command fails.check=True: raisesubprocess.CalledProcessErroron failure.- Prefer
['program', 'argument']over a command string. - Catch
FileNotFoundErrorif the program might not be installed. - Add
timeout=secondsfor commands that could hang. - Use
shell=Trueonly for trusted strings that truly need shell syntax.
FAQ
How do I capture both stdout and stderr in Python?
Use stdout=subprocess.PIPE and stderr=subprocess.STDOUT in subprocess.run(). Read the combined string from completed.stdout.
Why is my captured output empty when a command fails?
Many programs write failures to stderr, not stdout. Capture stderr too, either separately or by redirecting it to STDOUT.
Does a non-zero return code prevent me from reading output?
No. With check=False, subprocess.run() returns a result object containing both returncode and captured output.
Should I use os.system() to run a command?
Usually no. os.system() provides limited output handling. subprocess.run() is clearer, safer, and gives access to return codes and streams.
How do I run a command string instead of a list of arguments?
For a trusted simple string, parse it with shlex.split(command) and pass the result to . Avoid unless you need shell features such as pipes or redirects.
Mini Project
Description
Build a small command runner that executes a supplied program, captures normal and error output together, and prints a clear success or failure report. This mirrors the behavior needed by automation scripts and developer tools.
Goal
Create a reusable Python function that returns a command's exit code and complete text output.
Requirements
Requirement 1
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Add Rows to a Pandas DataFrame in Python
Learn how to add rows to a Pandas DataFrame, why repeated row appends are slow, and when to use loc, concat, or record lists.
Call a Function by Name in a Python Module
Learn how to call a function by name in a Python module using strings, getattr, and safe patterns for dynamic function dispatch.