1. subprocess 是什么? #
subprocess 是 Python 标准库,用于在 Python 程序中启动和管理子进程(其他程序或系统命令)。
- 日常场景包括:执行
ping 检测网络、调用 git 命令、运行打包脚本、获取命令行工具输出等。
| 能力 |
说明 |
| 创建子进程 |
在 Python 中运行外部命令 |
| 捕获输出 |
把 stdout/stderr 保存到变量 |
| 检查返回码 |
0 表示成功,非 0 表示失败 |
| 超时控制 |
超时自动终止子进程 |
2. subprocess.run() 基本用法 #
run() 是最推荐的入口:执行命令、等待完成、返回结果,适合 90% 的场景。
- 命令参数必须用列表传递,如
["ping", "-n", "2", "127.0.0.1"],每个元素对应一个参数。
- Windows 下内置命令(如
dir)需通过 cmd /c 调用:["cmd", "/c", "dir"]。
- 不传
capture_output 时,输出直接显示在终端,适合交互式调试。
import subprocess
subprocess.run(["cmd", "/c", "dir"])
import sys
subprocess.run([sys.executable, "-c", "print('hello')"])
4. run() 常用参数 #
args:要执行的命令及参数(通常是字符串列表)
capture_output=True:同时捕获标准输出(stdout)和标准错误(stderr)
text=True:输出以字符串(文本)而非字节形式返回
check=True 适合"命令失败就应该中断"的场景,配合 try/except 使用。
timeout 防止子进程卡死,网络请求、外部工具调用时建议设置。
shell=True 有注入风险,不推荐。
| 参数 |
作用 |
args |
命令列表,如 ["ping", "-n", "2", "host"] |
capture_output=True |
捕获 stdout 和 stderr |
text=True |
输出为字符串(否则为 bytes) |
check=True |
返回码非 0 时抛异常 |
timeout=N |
超时(秒)后终止 |
shell=True |
通过 shell 执行(不推荐) |
4.1 capture_output #
import subprocess
result = subprocess.run(
["ping", "-n", "2", "127.0.0.1"],
capture_output=True,
text=True,
timeout=5
)
print(result.stdout)
print("返回码:", result.returncode)
4.2 check #
check=True 在返回码非 0 时抛出 CalledProcessError,省去手动判断 returncode。
import subprocess
try:
result = subprocess.run(["cmd", "/c", "dir"], capture_output=True, text=True, check=True)
print(result.stdout)
except subprocess.CalledProcessError as e:
print(f"命令失败,返回码: {e.returncode}")
except subprocess.TimeoutExpired:
print("命令超时")
4.3 不关心输出时 #
import subprocess
subprocess.run(
["cmd", "/c", "dir"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
5. Popen 与实时输出 #
run() 会阻塞到命令结束;需要边执行边读取输出(如实时日志)时用 Popen。
stdout=subprocess.PIPE 创建管道,主进程可循环读取 proc.stdout 逐行获取输出。
- 读取完毕后调用
proc.wait() 等待子进程结束,再检查 proc.returncode。
- 向子进程发送输入用
communicate(input=...)。
import subprocess
import sys
proc = subprocess.Popen(
[sys.executable, "-c", "for i in range(3): print(i)"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
for line in proc.stdout:
print("实时:", line, end="")
proc.wait()
print("返回码:", proc.returncode)
6. 安全:避免 shell=True #
shell=True 会把命令交给系统 shell 解析,用户输入中的 &、|、; 等可能被当作命令分隔符执行。
- 命令注入是常见安全漏洞,始终用列表传参,让每个参数原样传递,不被 shell 解释。
- 只有明确需要 shell 特性(如通配符展开)时才考虑
shell=True,且绝不能拼接用户输入。
- Windows 下同样适用:用
["cmd", "/c", "dir"] 而非 shell=True + 字符串。
import subprocess
import sys
user_input = "hello & dir"
subprocess.run(f"echo {user_input}", shell=True)
import subprocess
import sys
user_input = "hello & dir"
subprocess.run([sys.executable, "-c", f"print({user_input!r})"])
!r 是 Python 中 f-string(格式化字符串)的一个转换标志。
它的作用是:在将变量插入字符串之前,先对该变量调用 repr() 函数,得到它的“可打印表示”(通常带引号,并对特殊字符进行转义)。
- 假设
user_input = "hello & dir"
- 则
f"print({user_input!r})" 实际生成的字符串是 print('hello & dir') (注意这里用的是单引号包裹,内部的 & 没有被当作 shell 命令分隔符)
- 最终 Python 执行的是:
print('hello & dir') —— 安全地打印出用户的输入内容,而不是执行 dir 命令。
如果不加 !r 而写成 f"print({user_input})",生成的字符串会是 print(hello & dir),这在 Python 中语法错误(hello 未定义),而且如果用于 shell=True 场景,还可能引发注入风险。
7. 常见异常 #
CalledProcessError:check=True 且命令返回非 0 时抛出,可通过 e.returncode 获取返回码。
TimeoutExpired:超过 timeout 秒未完成时抛出。
FileNotFoundError:命令对应的程序不存在,如拼写错误或程序未安装。
- 以上异常均可用
try/except 捕获,按需处理或向上抛出。
| 异常 |
触发条件 |
CalledProcessError |
check=True 且返回码 ≠ 0 |
TimeoutExpired |
超过 timeout 秒 |
FileNotFoundError |
找不到可执行程序 |
8. 总结 #
- 日常开发用
subprocess.run() + 列表传参 + capture_output=True + text=True。
- 需要失败即报错加
check=True,防止卡死加 timeout,实时输出用 Popen。
- 永远优先列表传参,避免
shell=True 防止命令注入。
8.1 速查 #
import subprocess, sys
result = subprocess.run(
["cmd", "/c", "dir"],
capture_output=True, text=True, check=True, timeout=30,
)
print(result.stdout)
subprocess.run(["cmd", "/c", "dir"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
subprocess.run([sys.executable, "script.py", "arg1"])
8.2 最佳实践 #
- 命令参数用列表,不用字符串拼接
- 外部命令设置合理
timeout,避免无限等待
- 用
check=True + try/except 明确处理失败情况
- Windows 内置命令通过
["cmd", "/c", "命令"] 调用