杰瑞科技汇

Python如何执行shell命令?

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

Python如何执行shell命令?-图1
(图片来源网络,侵删)

如何选择?

方法 适用场景 返回值 异常处理 安全性
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

缺点:

Python如何执行shell命令?-图2
(图片来源网络,侵删)
  • 不安全:如果命令的任何部分来自用户输入,可能会导致命令注入漏洞。
  • 功能有限:无法获取命令的输出,也无法与命令进行复杂的交互。

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()

缺点:

Python如何执行shell命令?-图3
(图片来源网络,侵删)
  • 同样不安全:和 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() - 更底层的控制

Popensubprocess 的底层构造函数,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.systemsubprocessshell=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,请务必对用户输入进行严格的清理和验证。
分享:
扫描分享到社交APP
上一篇
下一篇