在 Python 中执行 shell 命令有几种常见的方法,每种方法都有其适用场景,下面我将详细介绍这些方法,并提供代码示例和注意事项。

如何选择?
| 方法 | 适用场景 | 返回值 | 异常处理 | 安全性 |
|---|---|---|---|---|
os.system() |
简单执行命令,不关心输出 | 退出码 (int) | 简单 | 不安全 (有注入风险) |
os.popen() |
需要获取命令的输出 | 文件对象 | 简单 | 不安全 (有注入风险) |
subprocess 模块 |
强烈推荐,功能最全、最灵活 | 根据函数不同 | 完善 | 相对安全 (可避免注入) |
shutil 模块 |
执行特定类型的文件操作 | 无 | 简单 | 安全 (仅限预定义命令) |
os.system() - 最简单的方式
这是最直接、最简单的方法,它会在子 shell 中执行命令,并返回命令的退出状态码(0 表示成功,非 0 表示失败)。
特点:
- 简单易用:一行代码即可。
- 不获取输出:命令的输出会直接打印到你的控制台(标准输出和标准错误),但 Python 程序本身无法捕获这些输出。
- 返回退出码:只关心命令是否成功执行。
示例代码:
import os
# 执行一个简单的命令
# 注意:输出会直接显示在终端上
return_code = os.system('ls -l')
print(f"命令的退出码是: {return_code}")
# 执行一个会失败的命令
return_code = os.system('ls /non_existent_directory')
print(f"命令的退出码是: {return_code}") # 通常会返回非0
缺点:

- 不安全:如果命令的任何部分来自用户输入,可能会导致命令注入漏洞。
- 功能有限:无法获取命令的输出,也无法与命令进行复杂的交互。
os.popen() - 获取命令的输出
os.popen() 打开一个管道,允许你读取命令的输出或向命令的输入写入数据。
特点:
- 可以获取输出:返回一个文件对象,你可以像读取普通文件一样读取命令的输出。
- 用法已过时:在 Python 3.3 之后,官方文档推荐使用
subprocess模块来替代它。
示例代码:
import os
# 执行命令并获取输出
# 'r' 表示以只读模式打开管道
pipe = os.popen('ls -l')
# 读取所有输出
output = pipe.read()
# 关闭管道
pipe.close()
print("命令的输出是:")
print(output)
# 也可以逐行读取
# pipe = os.popen('ls -l')
# for line in pipe:
# print(line.strip())
# pipe.close()
缺点:

- 同样不安全:和
os.system()一样,存在命令注入的风险。 - 已过时:
subprocess提供了更强大、更灵活的接口。
subprocess 模块 - 现代、灵活、安全的选择
subprocess 是 Python 官方推荐用于生成子进程的模块,它功能强大,可以完全替代 os.system() 和 os.popen(),并且提供了更安全、更灵活的方式来管理子进程。
1 subprocess.run() - 推荐的通用方法 (Python 3.5+)
这是最现代、最推荐的接口,它提供了丰富的参数来控制子进程的行为。
特点:
- 灵活:可以指定是否捕获输出、是否检查返回码、如何处理输入等。
- 安全:可以通过
shell=False和传递参数列表来避免命令注入。 - 返回
CompletedProcess对象:该对象包含了命令的执行结果,如返回码、输出等。
基本用法:
import subprocess
# 1. 执行一个简单的命令,不关心输出
# check=True 会在命令返回非零退出码时抛出 CalledProcessError 异常
try:
subprocess.run(['ls', '-l'], check=True)
except subprocess.CalledProcessError as e:
print(f"命令执行失败,返回码: {e.returncode}")
# 2. 执行命令并捕获输出
# text=True 会将输出解码为字符串
# capture_output=True 会捕获标准输出和标准错误
result = subprocess.run(['echo', 'Hello from subprocess'], capture_output=True, text=True)
print(f"标准输出: {result.stdout}")
print(f"标准错误: {result.stderr}")
print(f"返回码: {result.returncode}")
# 3. 使用 shell=True (注意安全风险!)
# 当需要使用 shell 特性(如管道 |, 重定向 >)时,才使用 shell=True
# 如果命令来自用户输入,绝对不要使用 shell=True
command_with_pipe = 'ls -l | grep .py'
result = subprocess.run(command_with_pipe, shell=True, capture_output=True, text=True)
print(f"使用管道的输出:\n{result.stdout}")
2 subprocess.Popen() - 更底层的控制
Popen 是 subprocess 的底层构造函数,run() 等函数都是基于它构建的,当你需要更精细的控制时(与子进程进行实时交互),可以使用 Popen。
特点:
- 异步启动:
Popen启动子进程后会立即返回,不会等待命令执行完成。 - 交互能力:可以连接到子进程的标准输入、输出和错误流。
示例代码:
import subprocess
# 启动一个进程,但不等待它完成
proc = subprocess.Popen(['ping', '-c', '5', 'google.com'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 你可以在这里做其他事情...
# 等待进程结束,并获取输出
stdout, stderr = proc.communicate()
print("--- 进程已结束 ---")
print(f"标准输出:\n{stdout}")
if stderr:
print(f"标准错误:\n{stderr}")
print(f"最终返回码: {proc.returncode}")
shutil 模块 - 执行特定文件命令
shutil 模块提供了一些高级的文件操作,shutil.which() 和 shutil.copy() 等函数有时会隐式地调用 shell 命令,但它本身不直接用于执行任意 shell 命令,它更侧重于执行预定义的、与文件系统相关的操作。
安全警告:命令注入
当执行的命令包含来自不可信来源(如用户输入、网络请求、文件内容等)的字符串时,必须小心命令注入。
不安全的示例 (使用 os.system 或 subprocess 的 shell=True)
import subprocess
user_input = "malicious; rm -rf /" # 恶意输入
# 危险!这会执行 'ls -l' 和 'rm -rf /'
# subprocess.run(f'ls -l {user_input}', shell=True)
# 同样危险
# os.system(f'ls -l {user_input}')
安全的做法
将命令和参数作为列表传递给 subprocess,并设置 shell=False (这是默认值)。
import subprocess
user_input = "malicious; rm -rf /"
# 安全!Python 会将列表中的每个元素作为独立的参数传递
# 它会尝试执行 'ls -l',并将 'malicious; rm -rf /' 作为文件名
# 这通常不会成功,并且绝不会执行 'rm' 命令
try:
subprocess.run(['ls', '-l', user_input], check=True)
except FileNotFoundError:
print(f"文件未找到: {user_input}")
except subprocess.CalledProcessError as e:
print(f"命令执行失败,返回码: {e.returncode}")
最终建议
- 对于绝大多数新代码,请使用
subprocess.run()。 它是功能、安全性和易用性之间最好的平衡。 - 只有在需要与子进程进行复杂的实时交互时,才考虑使用
subprocess.Popen()。 - 尽量避免使用
os.system()和os.popen(),除非你维护的是非常古老的代码库。 - 永远不要将用户输入直接拼接到命令字符串中。 如果必须使用
shell=True,请务必对用户输入进行严格的清理和验证。
