subprocess与命令执行
subprocess 模块是 Python 2.4+ 引入的,用于替代旧的 os.system、os.popen 等函数。它提供了更强大、更安全的进程创建和管理接口,支持输入输出重定向、管道、超时控制等功能。
基本用法
subprocess.call:执行命令,等待完成,返回退出码
import subprocess
result = subprocess.call(["ls", "-la"])
print result # 0 表示成功
注意:参数用列表传递,避免 shell 注入。如果传入字符串,需要 shell=True:
# 安全方式(推荐)
subprocess.call(["echo", "hello"])
# 使用 shell(危险,避免用户输入)
subprocess.call("echo hello", shell=True)
subprocess.check_call:执行命令,失败时抛出 CalledProcessError
import subprocess
try:
subprocess.check_call(["ls", "nonexistent"])
except subprocess.CalledProcessError as e:
print "Command failed with code:", e.returncode
subprocess.check_output:执行命令,返回输出字符串
import subprocess
output = subprocess.check_output(["echo", "hello"])
print output # hello\n
# 捕获错误输出
try:
output = subprocess.check_output(
["ls", "nonexistent"],
stderr=subprocess.STDOUT
)
except subprocess.CalledProcessError as e:
print "Error output:", e.output
Popen 类
Popen 提供更灵活的控制:
import subprocess
# 启动进程
process = subprocess.Popen(
["python", "-c", "print 'hello'"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 等待完成并获取输出
stdout, stderr = process.communicate()
print stdout # hello\n
print process.returncode # 0
实时读取输出:
import subprocess
process = subprocess.Popen(
["ping", "-c", "4", "example.com"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 逐行读取
for line in iter(process.stdout.readline, ''):
print "Output:", line.strip()
process.wait()
输入输出重定向
import subprocess
# 写入 stdin
process = subprocess.Popen(
["cat"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
stdout, _ = process.communicate("hello world")
print stdout # hello world
# 重定向到文件
with open("output.txt", "w") as f:
subprocess.call(["echo", "hello"], stdout=f)
# 从文件读取输入
with open("input.txt", "r") as f:
subprocess.call(["cat"], stdin=f)
管道
import subprocess
# ps aux | grep python
p1 = subprocess.Popen(["ps", "aux"], stdout=subprocess.PIPE)
p2 = subprocess.Popen(["grep", "python"], stdin=p1.stdout, stdout=subprocess.PIPE)
p1.stdout.close() # 允许 p1 在 p2 退出时接收 SIGPIPE
output = p2.communicate()[0]
print output
超时控制
Python 2.7 的 subprocess 没有内置超时,需要手动实现:
import subprocess
import signal
class TimeoutError(Exception):
pass
def run_with_timeout(cmd, timeout_sec):
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def timeout_handler(signum, frame):
process.kill()
raise TimeoutError("Command timed out")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(timeout_sec)
try:
stdout, stderr = process.communicate()
signal.alarm(0) # 取消定时器
return stdout, stderr, process.returncode
except TimeoutError:
return "", "Timeout", -1
stdout, stderr, code = run_with_timeout(["sleep", "10"], 2)
print code # -1(超时)
注意:Windows 不支持 signal.SIGALRM,需要其他方式实现超时。
实际应用
执行 Git 命令:
import subprocess
def git_status():
try:
output = subprocess.check_output(["git", "status", "--short"])
return output.strip().split("\n")
except subprocess.CalledProcessError:
return []
except OSError:
return [] # git 未安装
print git_status()
批量处理文件:
import subprocess
import os
def convert_images(directory):
for filename in os.listdir(directory):
if filename.endswith(".png"):
input_path = os.path.join(directory, filename)
output_path = input_path.replace(".png", ".jpg")
subprocess.call([
"convert", input_path, output_path
])
print "Converted:", filename
检查命令是否存在:
import subprocess
def command_exists(cmd):
try:
subprocess.call([cmd, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return True
except OSError:
return False
print command_exists("python") # True
print command_exists("nonexistent") # False
安全注意事项
永远不要将用户输入直接拼接到 shell 命令中:
# 危险!命令注入
user_input = "; rm -rf /"
subprocess.call("echo " + user_input, shell=True) # 执行 echo; rm -rf /
# 安全
subprocess.call(["echo", user_input]) # 正确转义
使用列表参数传递时,subprocess 会自动处理转义,避免注入攻击。
与旧 API 的对比
| 旧 API | subprocess 替代 |
|---|---|
os.system(cmd) | subprocess.call(cmd, shell=True) |
os.popen(cmd) | subprocess.Popen(cmd, stdout=PIPE) |
commands.getoutput(cmd) | subprocess.check_output(cmd, shell=True) |
subprocess 统一了所有进程调用方式,是 Python 2.7 中执行外部命令的首选。