The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| Run an argument sequence | subprocess.run([program, '--flag', value], check=True) | View examples |
| Reuse this Python | subprocess.run([sys.executable, '-m', 'package'], check=True) | View examples |
| Capture both streams | result = subprocess.run(args, capture_output=True, text=True) | View examples |
| Choose text encoding | subprocess.run(args, capture_output=True, text=True, encoding='utf-8') | View examples |
| Merge stderr into stdout | subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) | View examples |
| Require success | result = subprocess.run(args, check=True) | View examples |
| Check after inspection | result.check_returncode() | View examples |
| Read the exit status | if result.returncode != 0: handle_failure(result.stderr) | View examples |
| Bound runtime | subprocess.run(args, timeout=10, check=True) | View examples |
| Handle expiration | except subprocess.TimeoutExpired as exc: report(exc.cmd, exc.timeout) | View examples |
| Send standard input | subprocess.run(args, input=payload, text=True, check=True) | View examples |
| Set child environment | subprocess.run(args, env={**os.environ, 'APP_MODE': 'test'}, check=True) | View examples |
| Set child directory | subprocess.run(args, cwd=workspace, check=True) | View examples |
| Start a managed process | with subprocess.Popen(args, stdout=subprocess.PIPE, text=True) as process: stdout, _ = process.communicate() | View examples |
| Drain process pipes | stdout, stderr = process.communicate(input=payload, timeout=10) | View examples |
| Discard a stream | subprocess.run(args, stdout=subprocess.DEVNULL, check=True) | View examples |
Process execution crosses a security and portability boundary: Python must pass arguments, streams, environment state, and failures to another program. Prefer subprocess.run() with an argument sequence and shell=False, select the executable deliberately, bound untrusted or unreliable work with a timeout, and treat every nonzero status as an intentional branch. Reach for Popen only when run() cannot express the required lifecycle, and use communicate() when pipes are involved so neither side deadlocks on a full buffer.
Step by step
Detailed examples
Pass arguments as data, not shell source
With shell=False, an argument sequence preserves spaces and metacharacters inside one value instead of interpreting them as shell operators. This is the default and should remain the default for ordinary programs. Never concatenate untrusted text into a shell command. If shell syntax is genuinely required, use only trusted templates, apply the quoting rules of that exact shell, and document why Python or a direct pipeline cannot replace it. sys.executable is the portable choice for launching the current Python runtime.
import subprocess
import sys
value = 'report name; $(not-a-command)'
result = subprocess.run(
[sys.executable, '-c', 'import sys; print(sys.argv[1])', value],
check=True,
capture_output=True,
text=True,
encoding='utf-8',
)
print(result.stdout.strip()) report name; $(not-a-command)Choose stream ownership and decoding explicitly
capture_output=True creates separate stdout and stderr pipes; text=True returns strings rather than bytes. Add encoding when the child contract specifies one, because a machine's locale is not an application protocol. Capture only bounded output: communicate() stores pipe data in memory, so direct large or unbounded output to a file, consume it incrementally with a deliberate design, or let it inherit the parent stream.
import subprocess
import sys
script = "import sys; print('ready'); print('note', file=sys.stderr)"
result = subprocess.run(
[sys.executable, '-c', script],
check=True,
capture_output=True,
text=True,
encoding='utf-8',
)
print(result.stdout.strip())
print(result.stderr.strip()) ready
noteMake failure policy visible at the call site
A completed child is not necessarily a successful child. Use check=True when zero is the only accepted status; CalledProcessError retains the command, return code, and any captured streams. Leave check=False only when the program intentionally uses multiple statuses and branch on returncode. An executable that cannot be started raises OSError, commonly FileNotFoundError, rather than CalledProcessError.
import subprocess
import sys
script = "import sys; print('invalid', file=sys.stderr); sys.exit(7)"
try:
subprocess.run(
[sys.executable, '-c', script],
check=True,
capture_output=True,
text=True,
encoding='utf-8',
)
except subprocess.CalledProcessError as error:
print(error.returncode)
print(error.stderr.strip()) 7
invalidTreat runtime limits as a separate failure mode
timeout bounds communication after process creation. subprocess.run() kills and waits for the child before re-raising TimeoutExpired, so it does not leave that direct child running. Process creation itself cannot be interrupted on every platform, and descendants launched by the child can require platform-specific process-group handling. Avoid unrealistically tiny production limits and log the command identity and configured budget without exposing secrets.
import subprocess
import sys
try:
subprocess.run(
[sys.executable, '-c', 'import time; time.sleep(0.2)'],
check=True,
timeout=0.02,
)
except subprocess.TimeoutExpired:
print('timed out') timed outControl stdin, environment, and directory without global mutation
The input parameter sends a complete string or byte payload and closes the child's stdin. env replaces, rather than augments, the inherited environment, so copy os.environ when the child still needs ordinary launch variables and override only approved keys. Keep secrets out of command arguments because process listings and logs can expose them; stdin or an inherited secure descriptor may be more appropriate. cwd changes the child context without calling os.chdir() in the parent.
import os
import subprocess
import sys
environment = {**os.environ, 'APP_MODE': 'test'}
script = "import os, sys; print(os.environ['APP_MODE']); print(sys.stdin.read().strip().upper())"
result = subprocess.run(
[sys.executable, '-c', script],
input='ready\n',
env=environment,
check=True,
capture_output=True,
text=True,
encoding='utf-8',
)
print(result.stdout, end='') test
READYUse Popen only for lifecycle control that run cannot provide
Popen supports incremental interaction, custom pipelines, polling, and long-lived children. When both stdout and stderr are pipes, communicate() is the safe whole-output pattern because calling wait() before draining a full pipe can deadlock. A Popen context manager closes standard file descriptors and waits on exit. For large streaming protocols, continuously consume every redirected stream and define cancellation, shutdown, and backpressure before deploying.
import subprocess
import sys
script = "print('first'); print('second')"
with subprocess.Popen(
[sys.executable, '-c', script],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding='utf-8',
) as process:
stdout, stderr = process.communicate(timeout=2)
if process.returncode != 0:
raise subprocess.CalledProcessError(process.returncode, process.args, stdout, stderr)
print('|'.join(stdout.splitlines()))
print(len(stderr)) first|second
0Local code tester
Build a safe argument vector
Construct and inspect a command without starting a child process or invoking a shell.
Press Run to load Python locally.
Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
- Python Software Foundationsubprocess — Subprocess managementdocs.python.org
- Python Software FoundationPEP 324 — subprocess: New process modulepeps.python.org
- Python Software Foundationsys.executable — Path to the Python interpreterdocs.python.org
- Python Software Foundationshlex — Simple lexical analysisdocs.python.org
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



