subprocess.run starts a child operation, waits for completion and reports its exit status or a waiting failure.
Python subprocess: argument vectors, exit codes and bounded fixtures
Operation contract
The fixture starts the current interpreter with an explicit argument vector and shell disabled. A literal dollar expression is printed as data instead of being evaluated by a command shell. A second child exits with status three; check=True turns that nonzero status into CalledProcessError. The caller reads the exit status without pretending a failed command succeeded.
Failure and ownership boundary
The child programs are fixed and emit tiny output. capture_output retains all child output, so this is not safe handling for arbitrary noisy commands. The timeout controls this run’s wait and child cleanup, not every descendant process an application may start. External tools need output limits, trusted executable selection and an OS-specific process-tree policy. Python process pools: importable workers and serialization costs is a separate API.
Working program
import subprocess
import sys
result = subprocess.run(
[sys.executable, "-I", "-c", "import sys; print(sys.argv[1])", "$(receipt)"],
capture_output=True, text=True, check=True, timeout=3,
)
print(result.stdout.strip())
try:
subprocess.run([sys.executable, "-I", "-c", "raise SystemExit(3)"],
check=True, capture_output=True, timeout=3)
except subprocess.CalledProcessError as failure:
print("exit:", failure.returncode)Output
$(receipt)
exit: 3Costs and limits
Child startup dominates these small operations. Captured output uses O(b) memory for b output bytes; timeout is not a byte budget.
Common Mistakes
- Do not interpolate untrusted fields into a shell command.
- capture_output can retain unbounded output from an untrusted executable.
Connected lessons
Python process pools: importable workers and serialization costs, Python exceptions: translate an input error without hiding its cause, Python receipt CLI project: parse options and report failed input.
