前言
大家好,我是 倔强青铜三 。欢迎关注我,微信公众号: 倔强青铜三。点赞、收藏、关注,一键三连!
Python的subprocess
模块是标准库中用于创建和管理子进程的终极武器。
相比老旧的os.system
和os.popen
,它提供了更强大、更灵活、更安全的进程控制能力。
今天我们就来彻底掌握这个能让你Python脚本调用任何系统命令的模块!
一、模块概述与核心价值
在Python中执行系统命令曾经是个头疼的问题。老方法如os.system("ls -l")
虽然简单,但只能获取返回码,无法捕获命令输出;os.popen()
能获取输出,但功能有限且不够安全。
subprocess
模块解决了所有这些问题,它不仅能执行命令,还能:
- 捕获命令的输出和错误
- 控制输入输出流
- 设置超时限制
- 管理进程环境
- 安全地处理命令参数
python
import subprocess
# 老方法:os.system只能获取返回码
import os
return_code = os.system("echo 'Hello from os.system'")
print(f"os.system返回码: {return_code}") # 只能得到执行状态,看不到输出
# 运行结果:
# Hello from os.system
# os.system返回码: 0
二、核心类与函数:Popen与run()
1. 基础类 subprocess.Popen
Popen
是subprocess
模块的底层类,提供了最灵活的进程控制方式。
python
import subprocess
# 最基本的Popen用法
process = subprocess.Popen(["echo", "Hello from Popen"])
process.wait() # 等待进程结束
print(f"进程已结束,返回码: {process.returncode}")
# 运行结果:
# Hello from Popen
# 进程已结束,返回码: 0
Popen
构造函数的关键参数:
args
: 要执行的命令,可以是字符串(不推荐)或列表(推荐)shell
: 是否通过系统shell执行命令stdout
,stderr
,stdin
: 控制输入输出流cwd
: 设置工作目录env
: 设置环境变量
2. 高级快捷函数 subprocess.run()
run()
是Python 3.5+引入的高级封装,简化了常见用例。
python
import subprocess
# 使用run()执行简单命令
result = subprocess.run(["echo", "Hello from run()"], capture_output=True, text=True)
print(f"返回码: {result.returncode}")
print(f"标准输出: {result.stdout.strip()}")
print(f"标准错误: {result.stderr}")
# 运行结果:
# 返回码: 0
# 标准输出: Hello from run()
# 标准错误:
run()
返回一个CompletedProcess
对象,包含:
returncode
: 命令的退出状态码stdout
: 标准输出内容(如果设置了capture_output或stdout=PIPE)stderr
: 标准错误内容(如果设置了capture_output或stderr=PIPE)
三、子进程的执行与控制
1. 同步执行与阻塞
默认情况下,run()
和Popen.wait()
都是阻塞的,会等待命令执行完成。
python
import subprocess
import time
print("开始执行命令...")
start_time = time.time()
# 执行一个会稍微延迟的命令
result = subprocess.run(["sleep", "2"], capture_output=True, text=True)
end_time = time.time()
print(f"命令执行完成,耗时: {end_time - start_time:.2f}秒")
print(f"返回码: {result.returncode}")
# 运行结果:
# 开始执行命令...
# 命令执行完成,耗时: 2.00秒
# 返回码: 0
2. 异步执行与非阻塞
虽然run()
是阻塞的,但我们可以使用Popen
实现非阻塞效果。
python
import subprocess
import time
# 启动一个长时间运行的进程但不等待
process = subprocess.Popen(["sleep", "3"])
print(f"进程已启动,PID: {process.pid}")
print("主程序继续执行其他任务...")
# 检查进程状态
for i in range(5):
return_code = process.poll() # 检查进程是否结束
if return_code is None:
print(f"第{i+1}秒检查: 进程仍在运行")
else:
print(f"第{i+1}秒检查: 进程已结束,返回码: {return_code}")
break
time.sleep(1)
# 确保进程结束
if process.poll() is None:
process.wait()
print("最终检查: 进程状态已确认")
# 运行结果:
# 进程已启动,PID: 12345 # 实际PID会不同
# 主程序继续执行其他任务...
# 第1秒检查: 进程仍在运行
# 第2秒检查: 进程仍在运行
# 第3秒检查: 进程已结束,返回码: 0
# 最终检查: 进程状态已确认
四、输入输出(I/O)的重定向与捕获
1. 标准输出(stdout)的捕获与处理
捕获命令输出是最常见的需求之一。
python
import subprocess
# 捕获命令输出
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)
print("命令输出:")
print(result.stdout[:200] + "...") # 只打印前200个字符避免输出过长
print(f"\n返回码: {result.returncode}")
print(f"输出字节长度: {len(result.stdout)}")
# 运行结果(示例,实际输出取决于你的目录内容):
# 命令输出:
# total 16
# -rw-r--r-- 1 user group 1234 Aug 19 10:00 example.py
# -rw-r--r-- 1 user group 5678 Aug 19 10:01 test.txt
# ...
#
# 返回码: 0
# 输出字节长度: 123
2. 标准错误(stderr)的捕获与合并
错误输出同样重要,特别是调试时。
python
import subprocess
# 执行一个会出错的命令
result = subprocess.run(["ls", "non_existent_file_12345"],
capture_output=True, text=True)
print("标准输出:", result.stdout)
print("标准错误:", result.stderr.strip())
print("返回码:", result.returncode)
# 运行结果:
# 标准输出:
# 标准错误: ls: non_existent_file_12345: No such file or directory
# 返回码: 1
3. 标准输入(stdin)的写入与交互
向命令传递输入数据。
python
import subprocess
# 向命令传递输入
process = subprocess.Popen(["grep", "hello"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True)
# 发送输入并获取输出
input_text = "This is a test\nHello world\nAnother line\n"
stdout, stderr = process.communicate(input=input_text)
print("输入文本:")
print(input_text)
print("\n过滤后的输出(grep 'hello'):")
print(stdout)
print("返回码:", process.returncode)
# 运行结果:
# 输入文本:
# This is a test
# Hello world
# Another line
#
# 过滤后的输出(grep 'hello'):
# Hello world
# 返回码: 0
五、错误处理与退出码管理
1. 子进程退出状态码
理解返回码是处理命令执行结果的关键。
python
import subprocess
# 成功的命令
success_result = subprocess.run(["true"]) # true命令总是返回0
print(f"true命令返回码: {success_result.returncode}")
# 失败的命令
try:
fail_result = subprocess.run(["false"], check=True) # false命令总是返回1
except subprocess.CalledProcessError as e:
print(f"false命令执行失败,返回码: {e.returncode}")
print(f"异常信息: {e}")
# 运行结果:
# true命令返回码: 0
# false命令执行失败,返回码: 1
# 异常信息: Command '['false']' returned non-zero exit status 1.
2. 异常处理
正确处理各种异常情况。
python
import subprocess
# 处理命令不存在的情况
try:
result = subprocess.run(["non_existent_command_123"], check=True)
except FileNotFoundError as e:
print(f"命令未找到错误: {e}")
except subprocess.CalledProcessError as e:
print(f"命令执行失败: {e}")
except Exception as e:
print(f"其他错误: {e}")
# 处理超时情况
try:
result = subprocess.run(["sleep", "5"], timeout=2) # 2秒后超时
except subprocess.TimeoutExpired as e:
print(f"命令执行超时: {e}")
# 运行结果(会触发FileNotFoundError或TimeoutExpired,取决于第一个异常):
# 命令未找到错误: [Errno 2] No such file or directory: 'non_existent_command_123'
# 或者
# 命令执行超时: Command '['sleep', '5']' timed out after 2 seconds
六、高级配置与扩展
1. 环境变量与工作目录
自定义命令执行环境。
python
import subprocess
import os
# 查看当前工作目录
print("当前Python脚本工作目录:", os.getcwd())
# 在指定目录中执行命令
result = subprocess.run(["pwd"], # Unix/Linux命令,显示当前目录
cwd="/tmp", # 在/tmp目录中执行
capture_output=True,
text=True)
print(f"/tmp目录下的pwd结果: {result.stdout.strip()}")
# 自定义环境变量
custom_env = os.environ.copy() # 复制当前环境
custom_env["MY_CUSTOM_VAR"] = "Hello from custom env"
result = subprocess.run(["env"], # 显示所有环境变量
env=custom_env, # 使用自定义环境
capture_output=True,
text=True)
print("\n环境变量中包含MY_CUSTOM_VAR:", "MY_CUSTOM_VAR" in result.stdout)
print("MY_CUSTOM_VAR的值:",
[line for line in result.stdout.split('\n') if "MY_CUSTOM_VAR" in line][0])
# 运行结果:
# 当前Python脚本工作目录: /your/current/path # 实际路径会不同
# /tmp目录下的pwd结果: /tmp
# 环境变量中包含MY_CUSTOM_VAR: True
# MY_CUSTOM_VAR的值: MY_CUSTOM_VAR=Hello from custom env
2. 超时控制
防止命令执行时间过长。
python
import subprocess
# 设置超时
try:
print("执行一个会超时的命令...")
result = subprocess.run(["sleep", "3"], timeout=1.5) # 1.5秒后超时
except subprocess.TimeoutExpired as e:
print(f"命令超时被终止: {e}")
print("超时命令信息:", e.cmd)
print("超时设置:", e.timeout)
# 运行结果:
# 执行一个会超时的命令...
# 命令超时被终止: Command '['sleep', '3']' timed out after 1.5 seconds
# 超时命令信息: ['sleep', '3']
# 超时设置: 1.5
3. Shell模式的 使用与风险
shell=True
的强大与危险。
python
import subprocess
# 不使用shell
print("不使用shell执行:")
result = subprocess.run(["echo", "Hello without shell"],
capture_output=True,
text=True)
print(result.stdout.strip())
# 使用shell (有风险!)
print("\n使用shell执行:")
try:
result = subprocess.run("echo 'Hello with shell'",
shell=True,
capture_output=True,
text=True)
print(result.stdout.strip())
# 危险示例: 命令注入风险(仅用于演示,实际不要这样做!)
user_input = "hello; ls -l" # 恶意用户可能输入这样的内容
print("\n危险示例 - 命令注入风险演示:")
dangerous_command = f"echo {user_input}" # 不安全的命令构造
result = subprocess.run(dangerous_command, shell=True, capture_output=True, text=True)
print("危险命令输出:", result.stdout)
except Exception as e:
print(f"错误: {e}")
# 运行结果:
# 不使用shell执行:
# Hello without shell
#
# 使用shell执行:
# Hello with shell
#
# 危险示例 - 命令注入风险演示:
# 危险命令输出: echo hello; ls -l
# hello; ls -l
⚠️ 重要警告 : shell=True
虽然方便(可以直接执行像ls -l
这样的字符串命令),但存在严重的安全风险!如果命令中包含用户输入,攻击者可能通过注入额外命令来执行恶意操作。尽可能使用shell=False
(默认)并通过列表传递参数。
七、最佳实践与常见问题
- 优先使用列表形式传递参数 :
["ls", "-l"]
比"ls -l"
更安全 - 避免不必要的shell=True:除非需要shell特性(如管道、通配符)
- 总是处理返回码和异常:检查命令是否成功执行
- 合理使用超时:防止命令挂起导致程序卡死
- 及时释放资源:确保子进程正确终止,避免僵尸进程
python
import subprocess
# 最佳实践示例: 安全地执行命令并处理结果
def safe_execute(command, timeout=30):
try:
# 使用列表形式,避免shell=True
result = subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=timeout,
check=True)
return {
"success": True,
"returncode": result.returncode,
"stdout": result.stdout,
"stderr": result.stderr
}
except subprocess.CalledProcessError as e:
return {
"success": False,
"error": "命令执行失败",
"returncode": e.returncode,
"stdout": e.stdout,
"stderr": e.stderr
}
except subprocess.TimeoutExpired:
return {
"success": False,
"error": f"命令执行超时(超过{timeout}秒)",
"returncode": None,
"stdout": None,
"stderr": None
}
except FileNotFoundError:
return {
"success": False,
"error": "命令不存在",
"returncode": None,
"stdout": None,
"stderr": None
}
except Exception as e:
return {
"success": False,
"error": f"未知错误: {str(e)}",
"returncode": None,
"stdout": None,
"stderr": None
}
# 测试最佳实践函数
print("测试成功命令:")
print(safe_execute(["echo", "This is a safe command"]))
print("\n测试失败命令:")
print(safe_execute(["false"]))
print("\n测试不存在的命令:")
print(safe_execute(["non_existent_command_123"]))
# 运行结果:
# 测试成功命令:
# {'success': True, 'returncode': 0, 'stdout': 'This is a safe command\n', 'stderr': ''}
#
# 测试失败命令:
# {'success': False, 'error': '命令执行失败', 'returncode': 1, 'stdout': '', 'stderr': ''}
#
# 测试不存在的命令:
# {'success': False, 'error': '命令不存在', 'returncode': None, 'stdout': None, 'stderr': None}
八、实战场景与应用
subprocess
模块在实际开发中有着广泛的应用:
- 自动化运维脚本:执行系统命令监控服务器状态
- 与命令行工具交互:调用git、docker、ffmpeg等工具
- 数据处理管道:将多个命令组合成处理流程
- 替代旧方法 :用更安全的
subprocess
替换os.system
和os.popen
python
import subprocess
# 实战示例: 获取系统信息并处理
def get_system_info():
try:
# 获取磁盘使用情况
df_result = subprocess.run(["df", "-h"],
capture_output=True,
text=True,
check=True)
# 获取内存使用情况 (Unix/Linux)
free_result = subprocess.run(["free", "-h"],
capture_output=True,
text=True,
check=True)
return {
"disk_usage": df_result.stdout,
"memory_usage": free_result.stdout,
"status": "success"
}
except Exception as e:
return {
"status": "error",
"error": str(e)
}
# 获取并打印系统信息
system_info = get_system_info()
if system_info["status"] == "success":
print("=== 磁盘使用情况 ===")
print(system_info["disk_usage"])
print("\n=== 内存使用情况 ===")
print(system_info["memory_usage"])
else:
print("获取系统信息失败:", system_info["error"])
# 运行结果(示例,实际输出取决于你的系统):
# === 磁盘使用情况 ===
# Filesystem Size Used Avail Use% Mounted on
# /dev/disk1s1 500G 100G 400G 20% /
# ...
#
# === 内存使用情况 ===
# total used free shared buff/cache available
# Mem: 8G 2G 4G 200M 2G 5G
# Swap: 2G 0B 2G
通过掌握subprocess
模块,你的Python脚本将获得强大的系统交互能力,能够轻松调用任何命令行工具,实现各种自动化任务。
记住遵循最佳实践,特别是安全方面的考虑,你就能成为Python系统编程的高手!
最后感谢阅读!欢迎关注我,微信公众号: 倔强青铜三 。
点赞、收藏、关注 ,一键三连!!欢迎 点击 【👍喜欢作者】 按钮进行 打赏💰💰,请我喝一杯咖啡☕️!