Python subprocess 模块
run vs Popen, capturing output, timeouts, and why shell=True is dangerous.
Key PointsPass a list, never a shell string; always set a timeout and check returncode.
subprocess.run
run executes and waits, returning a CompletedProcess with returncode, stdout and stderr.
Python
import subprocess
r = subprocess.run(["ls", "-l", "/tmp"], capture_output=True, text=True, timeout=10)
print(r.returncode)
print(r.stdout.splitlines()[0])Output
0
total 48Errors and Timeouts
A failing command does not raise — check returncode or pass check=True. Timeouts raise TimeoutExpired and kill the child.
Python
import subprocess
try:
subprocess.run(["false"], check=True, timeout=5)
except subprocess.CalledProcessError as exc:
print("failed with", exc.returncode)
try:
subprocess.run(["sleep", "5"], timeout=1)
except subprocess.TimeoutExpired:
print("timed out")Output
failed with 1
timed outShell Injection
shell=True hands the string to a shell, so ;, | and $() in user input get executed. A list argument never touches the shell.
Python
import subprocess
name = "a; rm -rf /" # 用户输入
# 危险:整个字符串进 shell
# subprocess.run(f"echo {name}", shell=True)
# 安全:列表参数,不经过 shell
r = subprocess.run(["echo", name], capture_output=True, text=True)
print(r.stdout.strip())Output
a; rm -rf /Streaming Output
Python
import subprocess
with subprocess.Popen(["ping", "-c", "2", "127.0.0.1"],
stdout=subprocess.PIPE, text=True) as proc:
for line in proc.stdout:
print(line.rstrip())
print("exit", proc.wait())If you truly need shell features, keep the command fixed and pass variables via
env=, never by concatenation.subprocess.run
run 是最常用的入口:执行完等它结束,返回 CompletedProcess,里面有 returncode、stdout、stderr。
Python
import subprocess
r = subprocess.run(["ls", "-l", "/tmp"], capture_output=True, text=True, timeout=10)
print(r.returncode)
print(r.stdout.splitlines()[0])Output
0
total 48错误与超时
命令失败不会自动抛异常,只体现在 returncode。想让它抛,传 check=True。超时会抛 TimeoutExpired,子进程会被杀掉。
Python
import subprocess
try:
subprocess.run(["false"], check=True, timeout=5)
except subprocess.CalledProcessError as exc:
print("failed with", exc.returncode)
try:
subprocess.run(["sleep", "5"], timeout=1)
except subprocess.TimeoutExpired:
print("timed out")Output
failed with 1
timed out命令注入
shell=True 会把字符串交给 /bin/sh 解析,用户输入里的 ;、|、$() 都会被执行。传列表则不会经过 shell。
Python
import subprocess
name = "a; rm -rf /" # 用户输入
# 危险:整个字符串进 shell
# subprocess.run(f"echo {name}", shell=True)
# 安全:列表参数,不经过 shell
r = subprocess.run(["echo", name], capture_output=True, text=True)
print(r.stdout.strip())Output
a; rm -rf /流式读取
Python
import subprocess
with subprocess.Popen(["ping", "-c", "2", "127.0.0.1"],
stdout=subprocess.PIPE, text=True) as proc:
for line in proc.stdout:
print(line.rstrip())
print("exit", proc.wait())需要 shell 特性(管道、通配符)时,用
shell=True 只传固定命令,把变量通过 env= 或参数传入,绝不拼字符串。