杰瑞科技汇

Python如何执行Linux命令?

在 Python 中执行 Linux 命令有多种方法,每种方法都有其适用场景,下面我将从最基础到最推荐的方式,为你详细介绍这些方法。

核心概念

在开始之前,需要理解两个关键概念:

  1. 标准输出: 命令执行后返回的文本结果。
  2. 标准错误: 命令执行过程中发生的错误信息。

一个健壮的脚本应该能同时捕获这两者。


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.systemos.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() ⭐⭐⭐ (兼容旧代码)

最佳实践建议

  1. 总是使用 subprocess.run() 作为你的默认选择。 它能满足 99% 的需求,并且是官方推荐的现代方法。
  2. 将命令和参数作为列表传递subprocess.run(['grep', 'hello', 'file.txt']),而不是 subprocess.run('grep hello file.txt'),这样可以避免 shell 注入漏洞。
  3. 明确指定 text=True,除非你确实需要处理原始字节。
  4. 使用 check=True 来处理命令执行失败的情况,这能让你的脚本更健壮。
  5. 仅在需要 shell 特性(如管道 command1 \| command2)时,才设置 shell=True,并确保你完全理解其中的安全风险。
分享:
扫描分享到社交APP
上一篇
下一篇