The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
Run an argument sequencesubprocess.run([program, '--flag', value], check=True)View examples
Reuse this Pythonsubprocess.run([sys.executable, '-m', 'package'], check=True)View examples
Capture both streamsresult = subprocess.run(args, capture_output=True, text=True)View examples
Choose text encodingsubprocess.run(args, capture_output=True, text=True, encoding='utf-8')View examples
Merge stderr into stdoutsubprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)View examples
Require successresult = subprocess.run(args, check=True)View examples
Check after inspectionresult.check_returncode()View examples
Read the exit statusif result.returncode != 0: handle_failure(result.stderr)View examples
Bound runtimesubprocess.run(args, timeout=10, check=True)View examples
Handle expirationexcept subprocess.TimeoutExpired as exc: report(exc.cmd, exc.timeout)View examples
Send standard inputsubprocess.run(args, input=payload, text=True, check=True)View examples
Set child environmentsubprocess.run(args, env={**os.environ, 'APP_MODE': 'test'}, check=True)View examples
Set child directorysubprocess.run(args, cwd=workspace, check=True)View examples
Start a managed processwith subprocess.Popen(args, stdout=subprocess.PIPE, text=True) as process: stdout, _ = process.communicate()View examples
Drain process pipesstdout, stderr = process.communicate(input=payload, timeout=10)View examples
Discard a streamsubprocess.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

01

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.

Preserve a metacharacter-rich argument literally
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())
Output
report name; $(not-a-command)
Back to quick reference ↑
02

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.

Capture diagnostics separately as UTF-8 text
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())
Output
ready
note
Back to quick reference ↑
03

Make 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.

Inspect a checked failure without losing stderr
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())
Output
7
invalid
Back to quick reference ↑
04

Treat 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.

Stop a child that exceeds its budget
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')
Output
timed out
Back to quick reference ↑
05

Control 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.

Provide text input and a scoped environment value
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='')
Output
test
READY
Back to quick reference ↑
06

Use 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.

Drain output and verify the final status
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))
Output
first|second
0
Back to quick reference ↑

Local code tester

Build a safe argument vector

Construct and inspect a command without starting a child process or invoking a shell.

Runs in your browser
Output
Press Run to load Python locally.

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. Python Software Foundationsubprocess — Subprocess managementdocs.python.org
  2. Python Software FoundationPEP 324 — subprocess: New process modulepeps.python.org
  3. Python Software Foundationsys.executable — Path to the Python interpreterdocs.python.org
  4. 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.

Share feedback