subprocess 是 Python 中用于创建和管理子进程的核心模块,它取代了旧的 os.system 和 os.spawn* 函数,提供了更强大、更灵活的子进程管理功能。
一、核心函数详解
1. subprocess.run()(推荐)
Python 3.5+ 引入的现代化接口,适合大多数场景
import subprocess
# 基本用法
result = subprocess.run(["git", "status"],
capture_output=True, # 捕获输出
text=True, # 输出为文本而非字节
check=True, # 非零退出码时抛出异常
timeout=30) # 超时设置
print("退出码:", result.returncode)
print("标准输出:", result.stdout)
print("错误输出:", result.stderr)
关键参数说明:
args:命令列表或字符串(推荐使用列表避免 shell 注入)stdin:输入源(默认 None,可选 PIPE 或文件对象)stdout/stderr:输出目标(PIPE、文件对象或 DEVNULL)cwd:设置工作目录env:自定义环境变量shell:是否通过系统 shell 执行(默认为 False)
2. subprocess.Popen()(高级控制)
提供更底层的进程控制
# 启动后台进程
process = subprocess.Popen(["python", "long_running.py"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
# 实时读取输出
while True:
line = process.stdout.readline()
if not line:
break
print(f"[实时输出] {line.strip()}")
# 等待完成并获取结果
return_code = process.wait(timeout=60)
print(f"进程结束,退出码: {return_code}")
常用方法:
poll():检查进程是否结束(返回 None 表示仍在运行)wait(timeout):等待进程结束communicate(input):交互式发送输入并获取输出terminate():发送 SIGTERM 信号kill():发送 SIGKILL 信号
3. 其他实用函数
# 1. 执行命令并获取退出码
exit_code = subprocess.call(["git", "pull"])
# 2. 检查执行结果(非零退出码时抛出异常)
subprocess.check_call(["python", "test.py"])
# 3. 获取命令输出(自动检查错误)
output = subprocess.check_output(["date", "+%Y-%m-%d"], text=True)
二、输入输出管道管理
1. 输入数据到子进程
# 发送输入数据
result = subprocess.run(["grep", "error"],
input="line1\nerror: something\nline3",
stdout=subprocess.PIPE,
text=True)
print(result.stdout) # 输出: error: something
2. 多级命令管道
# 执行: ps aux | grep python | awk '{print $2}'
ps = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE)
grep = subprocess.Popen(["grep", "python"], stdin=ps.stdout, stdout=subprocess.PIPE)
awk = subprocess.Popen(["awk", "{print $2}"], stdin=grep.stdout, stdout=subprocess.PIPE)
ps.stdout.close() # 关闭不需要的管道
grep.stdout.close()
pids = awk.communicate()[0].decode().split()
print("Python进程ID:", pids)
3. 输出重定向到文件
with open("output.log", "w") as f:
subprocess.run(["ls", "-l"], stdout=f)
# 错误输出重定向
with open("errors.log", "w") as err_file:
subprocess.run(["invalid_cmd"], stderr=err_file)
三、高级应用场景
1. 超时控制与错误处理
try:
result = subprocess.run(["ping", "google.com"],
timeout=5,
capture_output=True,
text=True)
except subprocess.TimeoutExpired:
print("命令执行超时!")
except subprocess.CalledProcessError as e:
print(f"命令执行失败: {e.stderr}")
2. 跨平台处理(Windows 特殊处理)
import sys
# Windows 下隐藏控制台窗口
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.run(["notepad"], startupinfo=startupinfo)
3. 环境变量管理
# 自定义环境变量
custom_env = {"PATH": "/usr/local/bin", "LANG": "en_US.UTF-8"}
subprocess.run(["echo", "$PATH"], env=custom_env, shell=True)
# 继承当前环境并添加新变量
new_env = {**os.environ, "DEBUG_MODE": "1"}
subprocess.run(["python", "app.py"], env=new_env)
四、安全最佳实践
1. 避免 Shell 注入
# 危险!用户输入可能执行恶意命令
user_input = "hello; rm -rf /"
subprocess.run(f"echo {user_input}", shell=True) # 可能删除文件!
# 安全做法
subprocess.run(["echo", user_input]) # 仅输出字符串
2. 安全执行用户命令
def safe_execute(command):
"""安全执行用户提供的命令"""
allowed_commands = {"ls", "date", "pwd"}
if not command:
raise ValueError("空命令")
cmd_parts = command.split()
if cmd_parts[0] not in allowed_commands:
raise PermissionError(f"禁止的命令: {cmd_parts[0]}")
return subprocess.run(cmd_parts, capture_output=True, text=True)
# 使用示例
result = safe_execute("date +%Y")
print("当前年份:", result.stdout.strip())
五、性能优化技巧
1. 批量执行减少进程创建
# 低效:多次创建进程
for file in files:
subprocess.run(["gzip", file])
# 高效:单进程处理所有文件
subprocess.run(["gzip"] + files)
2. 使用 Popen 保持长连接
# 与数据库客户端保持交互
with subprocess.Popen(["mysql", "-u", "user", "-p"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True) as db:
# 执行多条SQL
db.stdin.write("SELECT VERSION();\n")
db.stdin.write("SHOW DATABASES;\n")
db.stdin.close()
for line in db.stdout:
print("结果:", line.strip())
3. 异步处理
import asyncio
async def run_async():
process = await asyncio.create_subprocess_exec(
"python", "worker.py",
stdout=asyncio.subprocess.PIPE
)
# 实时读取输出
async for line in process.stdout:
print(f"收到: {line.decode().strip()}")
await process.wait()
# 运行异步任务
asyncio.run(run_async())
六、常见问题解决方案
1. 处理特殊字符
# Windows 路径问题
path = "C:\Program Files\App"
subprocess.run(["dir", path], shell=True) # 需要 shell=True
# Linux 特殊字符
subprocess.run(["echo", "含有$符的字符串"], shell=False) # 安全输出
2. 获取实时输出
process = subprocess.Popen(["ping", "google.com"],
stdout=subprocess.PIPE,
text=True)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
print(output.strip())
3. 后台进程管理
# 启动后台进程
process = subprocess.Popen(["python", "daemon.py"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
# 稍后终止
process.terminate() # 优雅终止
process.kill() # 强制终止
七、调试技巧
# 1. 打印实际执行的命令
cmd = ["ls", "-l"]
print("执行命令:", " ".join(cmd))
subprocess.run(cmd)
# 2. 使用日志记录
import logging
logging.basicConfig(level=logging.INFO)
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
logging.info("命令成功: %s", result.stdout)
except subprocess.CalledProcessError as e:
logging.error("命令失败: %s", e.stderr)
# 3. 交互式调试
subprocess.run(["bash"], shell=True) # 进入交互式 shell