subprocess.run()是Python 3.5起推荐的执行外部命令首选方式,安全、可控、功能完备,取代os.system()等旧方法。

subprocess.run() 是执行 Shell 命令的首选方式
Python 3.12 中 subprocess.run() 已完全成熟,推荐替代已弃用的 os.system() 或不安全的 shell=True + 字符串拼接。它默认不启用 shell 解析,更安全、可控性更强。
常见错误是直接传入字符串命令(如 "ls -l | grep py")并设 shell=True,这会引入注入风险,且在 Windows 上行为不一致。
- 简单命令用列表形式:例如
["ls", "-l"],无需shell=True - 需要管道或重定向时,避免拼接字符串;改用多个
subprocess.run()链式调用 +stdout=subprocess.PIPE - 若必须用 shell 功能(如通配符
*、&&),确保输入可信,并显式指定shell=True和executable(Linux/macOS 用/bin/sh,Windows 用cmd.exe)
Windows 批处理(.bat)文件要显式调用 cmd.exe
直接传 ["script.bat"] 通常失败——Python 不会自动查找 .bat 关联程序。必须让 cmd.exe 解释执行。
正确做法是构造明确的 shell 调用:
立即学习“Python免费学习笔记(深入)”;
subprocess.run(["cmd.exe", "/c", "script.bat"], check=True)
注意:/c 执行后退出,/k 会保持窗口打开(调试时有用)。不要省略 /c,否则 cmd.exe 可能挂起。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 路径含空格?用完整路径并确保字符串本身不被 shell 二次解析(即不加
shell=True) - 想捕获输出?加
capture_output=True, text=True - 环境变量需继承?默认已继承,如需修改,传
env={**os.environ, "MY_VAR": "value"}
捕获输出与错误码必须显式处理
subprocess.run() 默认不抛异常,即使命令返回非零退出码。很多人误以为失败会报错,结果静默忽略错误。
关键参数组合:
- 加
check=True:非零退出码触发subprocess.CalledProcessError - 加
capture_output=True:把stdout和stderr捕获到result.stdout/result.stderr - 加
text=True:返回字符串而非bytes,避免手动解码
示例:
try:
result = subprocess.run(
["python", "--version"],
capture_output=True,
text=True,
check=True
)
print("Python 版本:", result.stdout.strip())
except subprocess.CalledProcessError as e:
print("命令失败,退出码:", e.returncode)
print("错误输出:", e.stderr)
timeout 和编码问题在 Python 3.12 中更严格
Python 3.12 对 timeout 的处理更精确,超时后子进程会被强制终止(SIGKILL),但其子进程可能残留。另外,默认编码不再宽松 fallback,遇到非法字节会直接报 UnicodeDecodeError。
应对方法:
- 始终设
timeout=30类似值,防止挂起;超时后检查result.stderr是否为空,再决定是否重试 - 若目标命令输出不可控编码(如某些 Windows 工具输出 GBK),显式指定
encoding="gbk"或errors="replace" - 避免依赖
sys.getdefaultencoding()—— 它和实际终端编码无关,应以locale.getpreferredencoding()或明确协议为准
跨平台脚本里,shell=True 的行为差异、编码隐式转换、子进程残留,这三个点最容易在上线后暴露。别依赖“本地跑通就行”。

















