在 Python 中执行 Linux 命令有几种常见的方法,每种方法都有其适用场景,下面我将详细介绍这些方法,并提供代码示例和最佳实践建议。

方法概览
| 方法 | 函数 | 返回值 | 适用场景 |
|---|---|---|---|
os.system() |
os.system(command) |
退出状态码 (整数) | 简单执行命令,不关心命令的输出内容 |
os.popen() |
os.popen(command) |
文件对象,可读取输出 | 需要获取命令的输出内容,但语法较旧 |
subprocess 模块 |
subprocess.run(), subprocess.Popen() |
CompletedProcess 对象或 Popen 对象 |
现代、灵活、安全,是官方推荐的方法 |
shutil 模块 |
shutil.which(), shutil.copy() |
路径字符串等 | 用于执行特定的、与文件系统相关的命令 |
os.system() - 最简单直接
这是最简单、最古老的方法,它会在一个子shell中执行命令,并返回命令的退出状态码(0 表示成功,非 0 表示失败)。
特点:
- 优点: 语法非常简单,适合快速执行不关心输出的命令。
- 缺点: 无法直接获取命令的标准输出或标准错误内容,输出会直接打印到你的终端,不安全,容易受到 shell 注入攻击(如果命令参数来自用户输入)。
示例代码:
import os
# 执行一个简单的命令,不关心输出
# 返回值是命令执行后的退出码
return_code = os.system("ls -l")
print(f"命令执行退出码: {return_code}")
# 执行一个会出错的命令
return_code = os.system("ls /non_existent_directory")
print(f"错误命令的退出码: {return_code}")
os.popen() - 获取输出的旧方法
os.popen() 会打开一个管道,允许你像读取文件一样读取命令的输出。

特点:
- 优点: 可以方便地获取命令的标准输出。
- 缺点: 语法较旧,功能有限,无法同时获取标准输出和标准错误,同样存在 shell 注入的安全风险。
示例代码:
import os
# 打开一个管道执行命令
# 'r' 表示以读模式打开
pipe = os.popen("ls -l")
# 读取命令的输出
output = pipe.read()
# 关闭管道
pipe.close()
print("命令输出:")
print(output)
subprocess 模块 - 现代推荐方法
subprocess 模块是 Python 中执行外部命令的首选和最强大的工具,它旨在替代旧的 os.system 和 os.popen 函数,提供了更强大、更灵活、更安全的接口。
subprocess.run() - 推荐的通用函数
这是 Python 3.5+ 引入的函数,是大多数情况下的最佳选择。

关键参数:
args: 命令,可以是字符串或列表。强烈推荐使用列表形式,可以避免 shell 注入。capture_output=True: 捕获标准输出和标准错误。text=True: 将输出解码为文本(默认为字节)。check=True: 如果命令返回非零退出码(即失败),则抛出CalledProcessError异常。shell=False: 强烈建议保持为 False,除非你确实需要 shell 的特性(如管道、通配符)。
示例代码:
基本用法 - 获取输出
import subprocess
# 推荐使用列表形式传递命令,更安全
command = ["ls", "-l"]
# 执行命令并捕获输出
try:
# capture_output=True 会捕获 stdout 和 stderr
# text=True 会将输出解码为字符串
result = subprocess.run(command, capture_output=True, text=True, check=True)
# 访问标准输出
print("标准输出:")
print(result.stdout)
# 访问标准错误
# print("标准错误:")
# print(result.stderr)
# 访问返回码
print(f"退出码: {result.returncode}")
except subprocess.CalledProcessError as e:
# 当 check=True 且命令失败时,会抛出此异常
print(f"命令执行失败,退出码: {e.returncode}")
print(f"标准错误: {e.stderr}")
不关心输出,只关心是否成功
import subprocess
command = ["echo", "Hello from Python"]
# check=True 且命令失败,程序会在这里停止并报错
subprocess.run(command, check=True)
print("命令执行成功!")
subprocess.Popen() - 更底层的控制
当你需要更细粒度的控制时,比如与子进程进行实时交互(向一个程序发送输入),可以使用 Popen。
示例代码:
import subprocess
# 启动一个进程,但不等待它完成
# Popen 返回一个 Popen 对象,你可以用它来管理进程
process = subprocess.Popen(["ping", "-c", "4", "google.com"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
# 等待进程结束,并获取输出
stdout, stderr = process.communicate()
print("进程已结束。")
print("标准输出:")
print(stdout)
print("标准错误:")
print(stderr)
print(f"退出码: {process.returncode}")
shutil 模块 - 特定命令的便捷方法
shutil 模块提供了一些高级的文件和目录操作函数,其中一些底层会调用系统命令。
示例代码:
import shutil
# 查找可执行文件的路径 (类似 which 命令)
python_path = shutil.which("python3")
print(f"python3 的路径是: {python_path}")
# 复制文件 (类似 cp 命令)
# shutil.copy("source.txt", "destination.txt")
安全警告:Shell 注入
当你使用 os.system 或将 shell=True 用于 subprocess 时,如果命令的任何部分来自用户输入,就可能存在Shell 注入的安全风险。
危险示例:
import subprocess
user_input = "malicious; rm -rf /" # 恶意输入
# 危险!这会执行 "ls -l malicious; rm -rf /"
# subprocess.run(f"ls -l {user_input}", shell=True) # 错误示范
安全做法:
始终将命令作为列表传递,并避免使用 shell=True。
import subprocess user_input = "malicious; rm -rf /" # 安全!只会尝试查找一个名为 "malicious; rm -rf /" 的文件 # Shell 的特殊字符(如 ;, |, &)会被当作普通字符处理 subprocess.run(["ls", "-l", user_input]) # 正确示范
总结与最佳实践
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 快速执行一个命令,不关心输出 | os.system() |
代码最简单。 |
| 需要获取命令输出(旧代码) | os.popen() |
功能有限,不推荐用于新代码。 |
| 绝大多数情况 | subprocess.run() |
现代、灵活、安全,是官方推荐的首选。 |
| 需要与子进程实时交互 | subprocess.Popen() |
提供了对进程的底层控制能力。 |
| 查找可执行文件路径 | shutil.which() |
更 Pythonic,专为该任务设计。 |
核心建议:
- 优先使用
subprocess模块。 - 始终将命令作为列表传递,
["ls", "-l"],而不是字符串"ls -l",以防止 shell 注入。 - 除非必要,否则不要设置
shell=True。 - 使用
subprocess.run(..., check=True)可以优雅地处理命令执行失败的情况,避免手动检查返回码。
