Python subprocess 子进程完全指南
subprocess 让 Python 调用外部程序:run 执行命令、捕获输出、错误处理、传输入、shell 与列表参数的安全差异、Popen 高级控制。本文讲透每个细节。
直接回答:subprocess 是 Python 启动与控制外部进程的标准模块:run() 一行执行命令并拿结果,capture_output 捕获输出,check 自动抛错,Popen 提供流式交互的高级控制;接口跨平台可用,但外部命令、信号和 shell 行为仍因系统而异。
基本执行
import subprocess
result = subprocess.run(["ls", "-l", "/tmp"], capture_output=True, text=True)
print(result.stdout) # 标准输出
print(result.returncode) # 退出码 0 = 成功
三要点记牢:参数用列表(每个参数独立元素)、capture_output=True 拿输出、text=True 得到字符串而非字节。
错误处理
try:
subprocess.run(["git", "push"], check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as e:
print("命令失败:", e.returncode)
print("错误输出:", e.stderr)
check=True 让非零退出码直接抛异常——默认不抛,忘记检查 returncode 是脚本事故的常客。再加 timeout=30 防止命令卡死拖住整程序。
向子进程传输入
result = subprocess.run(
["grep", "error"],
input=log_text, # 通过 stdin 喂数据
capture_output=True,
text=True,
)
管道在 Python 层面完成,不需要 shell 拼接。
shell=True:能力越大风险越大
# 方便:通配符、管道、重定向都能用
subprocess.run("cat *.log | grep ERROR > errors.txt", shell=True)
# 危险:用户输入注进来就是命令注入
filename = input()
subprocess.run(f"cat {filename}", shell=True) # 输入 "; echo injected" 会被 shell 解释为额外命令
规则:优先参数列表且保持 shell=False。 参数列表仍需防止选项注入,例如支持 -- 的工具可使用 ["cat", "--", filename];生产环境还需限制可执行文件和可读路径。
Popen:高级控制
需要实时读输出、后台跑、双向交互时上 Popen:
proc = subprocess.Popen(
["python3", "worker.py"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # 合并输出,避免未消费的 stderr 管道阻塞
text=True,
)
# 流式读输出
for line in proc.stdout:
print("子进程说:", line.strip())
proc.wait()
print("退出码:", proc.returncode)
若输出有界,可改用 communicate(timeout=...) 一次读取两路输出,不能与上面的逐行消费方式混用。run(timeout=...) 超时会终止并等待直接子进程;Popen.communicate(timeout=...) 超时则不会自动终止,应在捕获 TimeoutExpired 后调用 kill(),再 communicate() 回收。进程树的后代还需平台相关的进程组管理。长任务记得消费输出管道——缓冲满了子进程会阻塞,双方死等。
环境变量与工作目录
import os
env = {**os.environ, "APP_ENV": "production"}
subprocess.run(
["make", "deploy"],
cwd="/srv/myapp", # 子进程的工作目录
env=env, # 定制环境
)
常见问题(FAQ)
Q:subprocess 和 os.system 的区别?
A:os.system 交给 shell 执行且控制能力有限;在 Unix 上返回的是 wait 状态,不是直接的退出码。需要参数、输入输出和错误控制时优先 subprocess。
Q:程序 hang 住不动?
A:一种常见原因是 PIPE 缓冲塞满:用了 stdout=PIPE 却不读。用 communicate() 或 run(capture_output=True),或输出重定向到文件。
Q:想并行跑多个命令?
A:多个 Popen 同时启动再逐个 wait;或用早于 Python 3.12 就已提供的 asyncio.create_subprocess_exec 走异步路线,更优雅。
官方参考
本文基于官方文档整理,未进行运行时或性能测试。示例中的业务函数、数据模型和部署地址需结合项目补全;局部片段不等同于完整生产应用。