在 Python 中执行 Linux 命令有多种方法,每种方法都有其适用场景,下面我将从最基础到最推荐的方式,为你详细介绍这些方法。
核心概念
在开始之前,需要理解两个关键概念:
- 标准输出: 命令执行后返回的文本结果。
- 标准错误: 命令执行过程中发生的错误信息。
一个健壮的脚本应该能同时捕获这两者。
os.system() - 最简单,但不推荐
这是最直接、最简单的方法,它就像在终端里直接输入命令一样。
- 特点:
- 执行命令,但无法获取命令的输出结果。
- 返回命令的退出状态码(0 表示成功,非 0 表示失败)。
- 会直接将命令的输出(包括标准输出和标准错误)打印到你的 Python 脚本的终端上。
- 适用场景:当你只想执行一个命令,不关心它的返回值,也不需要捕获其输出时(创建一个目录、设置一个环境变量)。
- 缺点:无法将命令的输出作为字符串在 Python 程序中进行后续处理。
import os
# 执行一个简单的命令,打印当前工作目录
# 注意:输出会直接显示在终端,而不是被变量捕获
return_code = os.system('pwd')
print(f"命令执行完毕,退出状态码是: {return_code}")
os.popen() - 可以获取输出,但已过时
os.popen() 打开一个管道,允许你执行命令并读取或写入它的标准输入/输出。
- 特点:
- 可以获取命令的标准输出。
- 返回一个文件对象,你需要调用
.read()方法来读取输出。 - 需要手动关闭管道(
p.close())。
- 适用场景:比
os.system()更灵活,可以获取输出,但已经有更好的替代方案。 - 缺点:语法稍显繁琐,且被认为是“过时”的 API,官方文档推荐使用
subprocess模块。
import os
# 使用 'r' 模式打开管道,用于读取命令的输出
p = os.popen('ls -l')
# 读取所有输出
output = p.read()
print("获取到的输出是:")
print(output)
# 关闭管道
p.close()
# 也可以获取退出状态码
status = p.close()
if status:
print(f"命令执行失败,状态码: {status}")
else:
print("命令执行成功")
subprocess 模块 - 现代、灵活、强大的首选
这是目前官方推荐、功能最全面、最安全的方法,它旨在替代 os.system 和 os.popen 等旧方法。
subprocess 提供了多个函数,以满足不同的需求。
1 subprocess.run() - 推荐的通用方法 (Python 3.5+)
这是最常用、最推荐的函数,它封装了所有常用功能,非常灵活。
基本用法:执行命令并获取输出
import subprocess
# 1. 执行一个简单的命令,获取输出
# check=True: 如果命令返回非零退出码(即失败),会抛出 CalledProcessError 异常
# text=True: 将输出解码为文本(字符串),而不是字节
# capture_output=True: 捕获标准输出和标准错误
try:
result = subprocess.run(['ls', '-l'], check=True, text=True, capture_output=True)
print("命令执行成功!")
print(f"标准输出:\n{result.stdout}")
# print(f"标准错误:\n{result.stderr}") # 如果命令有错误输出,可以在这里查看
print(f"返回码: {result.returncode}")
except subprocess.CalledProcessError as e:
print(f"命令执行失败,返回码: {e.returncode}")
print(f"标准错误:\n{e.stderr}")
重要参数说明:
args: 命令列表。最佳实践是始终将命令和参数作为列表传递,这样可以避免 shell 注入的风险。['ls', '-l']而不是'ls -l'。shell=False(默认): 不通过 shell 执行,更安全,如果需要使用 shell 特性(如管道 、通配符 ),需要设置为True,但要小心注入风险。capture_output=True: 捕获标准输出和标准错误。stdout/stderr: 可以分别指定捕获到的输出。stdout=subprocess.PIPE。text=True: 将输出解码为文本,默认是字节,可以用universal_newlines=True达到同样效果。check=False(默认): 如果命令返回非零退出码,不抛出异常,设置为True时,会抛出CalledProcessError异常。input: 可以向命令的标准输入传递字符串。
2 subprocess.Popen() - 最底层、最灵活
run() 函数实际上是对 Popen() 的封装,当你需要更精细的控制时(与进程进行实时交互、并行运行多个命令),就需要使用 Popen。
- 特点:
- 不会等待命令执行完成就返回,而是立即返回一个
Popen对象。 - 你需要手动调用
poll()或wait()来检查进程状态。 - 可以通过
communicate()方法来获取输出和错误,并等待进程结束。
- 不会等待命令执行完成就返回,而是立即返回一个
import subprocess
# 使用 Popen 启动进程
process = subprocess.Popen(['ls', '-l'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# communicate() 会等待进程结束,然后返回 (stdout, stderr) 元组
stdout, stderr = process.communicate()
print("命令执行完毕")
print(f"标准输出:\n{stdout}")
if stderr:
print(f"标准错误:\n{stderr}")
print(f"返回码: {process.returncode}")
3 subprocess.call() 和 subprocess.check_call()
这是 run() 出现之前的函数,现在仍然可用,但功能不如 run() 强大。
subprocess.call(): 执行命令,等待其完成,返回退出码,不捕获输出。subprocess.check_call(): 类似call(),但如果命令返回非零退出码,会抛出CalledProcessError异常。
import subprocess
# call() 的用法
return_code = subprocess.call(['echo', 'Hello from call'])
print(f"call() 的返回码: {return_code}")
# check_call() 的用法
try:
subprocess.check_call(['false']) # 'false' 命令总是返回非零
except subprocess.CalledProcessError as e:
print(f"check_call() 捕获到错误: {e}")
总结与最佳实践
| 方法 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
os.system() |
极其简单 | 无法获取输出,不安全 | ⭐ (仅用于简单、无交互的场景) |
os.popen() |
可以获取输出 | 语法繁琐,已过时 | ⭐⭐ (不推荐,有更好的替代) |
subprocess.run() |
功能全面、安全、推荐、语法清晰 | 稍微复杂一点(但值得学习) | ⭐⭐⭐⭐⭐ (首选,强烈推荐) |
subprocess.Popen() |
最灵活,支持高级交互 | 使用复杂,需要手动管理 | ⭐⭐⭐⭐ (用于复杂场景) |
subprocess.call() |
简单,等待完成 | 功能比 run() 少 |
⭐⭐⭐ (兼容旧代码) |
最佳实践建议
- 总是使用
subprocess.run()作为你的默认选择。 它能满足 99% 的需求,并且是官方推荐的现代方法。 - 将命令和参数作为列表传递。
subprocess.run(['grep', 'hello', 'file.txt']),而不是subprocess.run('grep hello file.txt'),这样可以避免 shell 注入漏洞。 - 明确指定
text=True,除非你确实需要处理原始字节。 - 使用
check=True来处理命令执行失败的情况,这能让你的脚本更健壮。 - 仅在需要 shell 特性(如管道
command1 \| command2)时,才设置shell=True,并确保你完全理解其中的安全风险。
