subprocess 模块用于生成子进程、连接其输入/输出/错误管道并获取其返回码的强大工具,旨在替代一些旧的模块(如 os.system, os.spawn*)。
1. 核心概念:为什么要用 subprocess?
在 Python 中,有时你需要与系统外部的命令或程序进行交互,例如调用 ls, grep, 甚至另一个 Python 脚本或二进制可执行文件。subprocess 模块提供了一种一致且安全的方式来创建和管理这些子进程。
核心目标:启动一个新的进程(子进程),并与它进行通信(发送输入、获取输出和错误信息),并等待它完成。
2. 推荐使用的核心函数:run()
Python 3.5 引入了 subprocess.run() 函数,这是目前推荐使用的、最高阶的 API,它能够处理大多数常见用例。
基本语法
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None,
capture_output=False, shell=False, cwd=None, timeout=None,
check=False, encoding=None, errors=None, text=None, env=None)简单示例:运行一个命令
import subprocess
# 运行 'ls -l' 命令
result = subprocess.run(['ls', '-l'])
# 默认情况下,输出会直接打印到终端。
# result 是一个 CompletedProcess 对象
print(result.returncode) # 查看命令的退出状态码,0 通常表示成功常用参数详解
args: 最重要参数。指定要执行的命令。
- 强烈建议传递一个列表,例如
['ls', '-l']。这是最安全的方式,可以避免 shell 注入攻击。 - 也可以传递一个字符串,但必须同时设置
shell=True,例如run('ls -l', shell=True)。除非有明确理由,否则应避免使用shell=True,因为它有安全风险。
stdout,stderr: 控制子进程的输出和错误流。
subprocess.PIPE: 捕获输出,以便在 Python 代码中访问。subprocess.DEVNULL: 将输出重定向到特殊文件/dev/null(即丢弃输出)。None(默认): 输出不重定向,直接打印到终端。- 一个已经打开的文件对象:将输出写入该文件。
capture_output: 如果设为True,相当于同时设置stdout=subprocess.PIPE和stderr=subprocess.PIPE。这是 Python 3.7 的新特性,非常方便。check: 如果设为True,并且子进程以非零状态码退出,将抛出一个CalledProcessError异常。这对于确保命令成功执行非常有用。text(或encoding,errors): 如果设为True,输入/输出流将以文本字符串(而不是字节序列bytes)的形式处理。encoding和errors参数用于指定编解码器和错误处理方案(类似于open()函数)。cwd: 在运行子进程之前,将工作目录切换到cwd所指定的路径。timeout: 设置命令超时时间(秒)。如果进程在超时后仍未结束,将抛出TimeoutExpired异常。input: 传递给子进程的输入数据。如果text=True,则应为字符串;否则应为字节序列。通常与stdin=subprocess.PIPE一起使用。env: 一个字典,用于为子进程定义环境变量。例如env={'MY_VAR': 'value'}。默认情况下,子进程会继承当前进程的环境变量。
3. 实战示例
示例 1:捕获输出
import subprocess
# 捕获标准输出
result = subprocess.run(['echo', 'Hello World!'],
capture_output=True,
text=True)
print(f"Output: {result.stdout}") # 输出: Hello World!
print(f"Return code: {result.returncode}") # 输出: 0示例 2:处理错误(check=True)
import subprocess
try:
# 运行一个肯定会失败的命令
result = subprocess.run(['ls', '/nonexistent/directory'],
capture_output=True,
text=True,
check=True) # 如果失败会抛出异常
except subprocess.CalledProcessError as e:
print(f"Command failed with return code {e.returncode}")
print(f"Error message: {e.stderr}") # ls: cannot access '/nonexistent/directory': No such file or directory示例 3:传递输入给子进程(与进程交互)
import subprocess
# 与 'grep' 命令交互,向其传递输入
input_text = """apple
banana
grape
orange
"""
# 让 grep 从标准输入读取,并搜索 ‘ap’
result = subprocess.run(['grep', 'ap'],
input=input_text,
capture_output=True,
text=True)
print(result.stdout) # 输出: apple\ngrape\n示例 4:超时控制
import subprocess
try:
# 运行一个会休眠 10 秒的命令,但我们只等 2 秒
result = subprocess.run(['sleep', '10'],
timeout=2,
capture_output=True)
except subprocess.TimeoutExpired:
print("Command timed out! It was killed.")示例 5:修改工作目录和环境变量
import subprocess
# 在指定目录下运行命令
result1 = subprocess.run(['pwd'], cwd='/tmp')
# 使用自定义环境变量运行命令
custom_env = {'MY_MESSAGE': 'Hello from subprocess'}
result2 = subprocess.run(['env'], env=custom_env, capture_output=True, text=True)
print(result2.stdout) # 会看到 MY_MESSAGE=Hello from subprocess 以及其他环境变量4. 底层组件:Popen 类
subprocess.run() 实际上是在 Popen 类的基础上构建的一个便利函数。对于更高级的用例(例如需要实时处理输出流,或者进行复杂的管道连接),你需要直接使用 Popen 类。
Popen 基本用法
import subprocess
# 启动进程
process = subprocess.Popen(['python3', '-c', 'import time; time.sleep(5); print("Done")'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
# 等待进程结束,并获取输出和错误
stdout, stderr = process.communicate()
print(f"STDOUT: {stdout}")
print(f"STDERR: {stderr}")
print(f"Return code: {process.returncode}")Popen 的强大之处在于 communicate() 方法,它处理了所有管道交互,并等待进程结束。你也可以使用 process.poll() 来非阻塞地检查进程是否已结束,或者逐行读取输出(for line in process.stdout:)。
5. 安全警告:关于 shell=True
尽量避免使用 shell=True。
- 安全风险:如果你使用
shell=True并且命令字符串来自用户输入,攻击者可能会执行任意 shell 命令(这被称为 shell 注入攻击)。
- 危险示例:
subprocess.run(f"echo {user_input}", shell=True)
如果user_input是"; rm -rf / #",后果不堪设想。
- 性能开销:启动一个 shell(如
/bin/sh)来解析你的命令字符串会产生额外的开销。 - 何时使用:只有当你需要用到 shell 的特性时,例如通配符 (
*)、管道 (|)、重定向 (>,<)、变量扩展 ($VAR) 等。
# 必要时使用 shell=True 的示例:使用管道
result = subprocess.run('ps aux | grep python',
shell=True,
capture_output=True,
text=True)总结与最佳实践
- 首选
subprocess.run():对于绝大多数情况,这个函数就足够了。 - 使用列表传递参数:
run(['cmd', 'arg1', 'arg2'])而不是run('cmd arg1 arg2', shell=True),以避免安全风险。 - 善用
capture_output=True和text=True:这是捕获输出并将其作为字符串处理的最简洁方式。 - 使用
check=True:如果你关心命令是否成功执行,这比手动检查returncode更 Pythonic。 - 谨慎使用
shell=True:清楚其风险,并仅在必要时使用。 - 需要高级控制时使用
Popen:例如需要与进程进行复杂的、实时的交互时。
subprocess 模块是 Python 与系统交互的瑞士军刀,掌握它将极大扩展你的脚本能力。
















