Running External Programs with subprocess
Run child processes safely with argument lists, captured streams, exit-code handling, and timeouts using Python's subprocess module.
Intermediate
Sometimes a Python program needs to run another program and use its result. The subprocess module starts a child process and lets your Python code inspect its output and exit status. For the common case, the official documentation recommends subprocess.run(); use the lower-level Popen interface only when you need more control over a process while it is running.
Start with subprocess.run()
Pass the command as a sequence of arguments. sys.executable selects the Python interpreter running this example, so the child does not depend on a platform-specific command name. capture_output=True collects standard output and standard error, text=True decodes captured streams to strings, and check=True raises an exception if the child exits unsuccessfully.
Output
Ready to run.
Keep arguments separate and avoid the shell
With the default shell=False, subprocess passes each sequence item as a separate argument rather than asking a shell to interpret it. Spaces, dollar signs, and semicolons inside an argument remain ordinary characters. Do not build a shell command by concatenating user input; shell=True introduces shell parsing and can create command-injection vulnerabilities. One platform caveat matters: Windows may route .bat or .cmd files through a system shell even when shell=False, so do not treat that setting as protection for untrusted arguments to batch files.
Output
Ready to run.
Send input and capture output
Use input= to send data to the child's standard input. With text=True, supply and receive strings. capture_output=True collects both stdout and stderr; choose stdout=PIPE and stderr=PIPE individually only when you need to configure them separately. The streams may be empty, and stderr is not the same as a Python exception.
Output
Ready to run.
Choose how to handle a nonzero exit
A completed process has a returncode. Zero conventionally indicates success, but your program must interpret the child's documented contract. With check=False, inspect returncode and captured stderr yourself. With check=True, a nonzero status raises subprocess.CalledProcessError, which exposes the return code and captured output when available.
Output
Ready to run.
Set a time limit and know what remains deferred
A timeout prevents run() from waiting indefinitely: when the limit expires, subprocess.TimeoutExpired is raised after the child is terminated and waited for. A timeout is not a complete sandbox and does not make an unsafe command safe. This lesson focuses on run(); streaming large amounts of data, interacting with a long-running process, process groups, and low-level Popen lifecycle control need a separate, more advanced treatment. Process creation itself may take longer than the requested limit on some operating systems, so timeout is not a strict real-time guarantee.
Output
Ready to run.
Quick Check
Quick check
Which approach should be the default for launching a known program with separate arguments?
import subprocess
import sys
result = subprocess.run(
[sys.executable, "-c", "print('safe')"],
capture_output=True, text=True, check=True, timeout=5
)
print(result.stdout.strip())
Quick check
What does capture_output=True do when subprocess.run() completes?
import subprocess, sys
result = subprocess.run([sys.executable, "-c", "print('out'); print('err', file=__import__('sys').stderr)"], capture_output=True, text=True, timeout=5)
print(result.stdout.strip(), result.stderr.strip())
Quick check
Which exception does check=True raise when the child returns a nonzero status?
import subprocess, sys
try:
subprocess.run([sys.executable, "-c", "raise SystemExit(3)"], check=True, timeout=5)
except subprocess.CalledProcessError as error:
print(error.returncode)
Quick check
What is the effect of timeout=1 for a child process that does not finish in time?
import subprocess, sys
try:
subprocess.run([sys.executable, "-c", "import time; time.sleep(2)"], timeout=0.05)
except subprocess.TimeoutExpired:
print('stopped waiting')
Quick check
Why is shell=True usually inappropriate when arguments include untrusted input?
import subprocess, sys
value = "semi;colon $HOME"
result = subprocess.run([sys.executable, "-c", "import sys; print(sys.argv[1])", value], capture_output=True, text=True, check=True, timeout=5)
print(result.stdout.strip())
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.