HOWTO · Python

如何在 Python 中运行 Bash 脚本

学习如何使用我们的综合指南通过 Python 执行 Bash 脚本。探索像 subprocess、os.system 和 subprocess.Popen 等方法,以高效地运行 Bash 命令。通过将 Bash 脚本集成到 Python 应用程序中,提高您的自动化技能并简化工作流程。

本页内容

对于大多数需要在 Python 继续执行前运行完毕的 Bash 脚本,使用 subprocess.run。它支持分别传递参数、捕获 stdout 和 stderr,并检查进程的 returncode。运行环境必须已安装 Bash,且 Python 能够找到它,因此这种方式依赖主机环境,在没有 Bash 的系统上并不适用。下面的示例会创建脚本文件,将姓名作为参数传入,并显示捕获到的结果。

方法 1:使用 subprocess 模块

这个独立示例会创建一个小型 Bash 脚本,向它传递一个参数,并捕获两个输出流。Bash 脚本会打印问候语,随后 Python 会打印捕获到的文本和进程状态。请注意,未更改的示例中,显示的问候语和 “Return code” 状态标签仍为英文。

import subprocess
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    script_path = Path(directory) / "greet.sh"
    script_path.write_text(
        '#!/usr/bin/env bash\nprintf "Hello, %s!\\n" "$1"\n',
        encoding="utf-8",
    )

    result = subprocess.run(
        ["bash", str(script_path), "Ada"],
        capture_output=True,
        text=True,
        check=False,
    )

print(result.stdout, end="")
print(f"Return code: {result.returncode}")
Hello, Ada!
Return code: 0

参数列表将脚本路径和 Ada 作为两个独立参数传递。默认情况下,shell=False 会让 Python 直接启动 Bash,而不是让 shell 解析该列表。这可以避免 shell 解析这些参数,但并不意味着绝对安全:请验证输入,并考虑脚本本身的行为。returncode 为 0 表示成功;非零值表示进程报告了失败。stderr 是单独的诊断信息流,其中出现文本本身并不代表命令失败。设置 capture_output=True 后,可以从结果对象中获取 stdout 和 stderr。

方法 2:使用 os.system

只有在命令较简单且 Python 不需要捕获其输出时,才使用 os.system。它接受一个命令字符串,通过系统 shell 运行命令,并将命令的输出流发送到当前进程的标准流。它的返回值取决于系统,可能编码的是进程状态,而不一定等于子进程的退出码,因此不能把它当作可移植的直接退出码。它不会提供可直接读取 stdout 或 stderr 的结果对象。不要用不可信输入拼接命令字符串,因为 shell 解析可能会赋予这些输入特殊含义。若需要分别传递参数、捕获输出流并可靠地检查状态,请优先使用 subprocess。

方法 3:使用 subprocess.Popen

如果需要对运行中的进程进行更底层的控制,例如管理其生命周期或连接管道,请选择 subprocess.Popen。对于有限的一次数据交互,创建进程时设置 stdin=subprocess.PIPE,然后调用 communicate(input=...) 发送输入,并在进程结束后收集捕获到的输出流。如果没有设置该标准输入管道,communicate(input=...) 就无法向子进程发送数据。它不会实时传递输出。如果为两个输出流都创建了管道,请使用 communicate() 读取它们;子进程运行期间,如果管道填满而未被读取,等待子进程可能导致死锁。若要实时流式处理输出,应有意地读取并排空这些输出流,不要把 communicate() 当作流式处理接口。具体管道行为取决于平台和进程。

结论

对于会运行至结束的 Bash 脚本,subprocess.run 通常是最清晰的选择:传递参数列表,在需要时捕获输出流,并通过 returncode 判断是否成功。对于无需处理输出的简单 shell 命令,可以使用 os.system;需要显式控制进程或与其通信时,则使用 subprocess.Popen。请确认运行 Python 程序的环境已安装 Bash,且 Python 能找到 Bash 可执行文件;否则无法启动脚本。

常见问题

  1. 从 Python 运行 Bash 脚本的最佳方式是什么?
    对于大多数会在 Python 继续执行前结束的脚本,使用 subprocess.run。它支持参数列表、捕获输出流,并通过 returncode 提供进程状态。

  2. 如何捕获脚本的输出?
    在 subprocess.run 中设置 capture_output=True 和 text=True,然后从返回对象读取 stdout 和 stderr。使用 Popen 时,连接所需的管道并调用 communicate(),即可在进程结束后收集输出流;这会收集输出,而不会实时流式传递。即使进程成功,stderr 中也可能包含诊断信息,因此请使用 returncode 检查状态。

  3. 传递参数列表安全吗?
    设置 shell=False 时,参数列表可以避免 shell 解析所提供的参数。这会降低 shell 注入风险,但不能保证所有脚本或输入都安全;请验证参数值,并考虑脚本本身的行为。

  4. 可以向 Bash 脚本传递参数吗?
    可以。在 subprocess 参数列表中,将每个参数作为独立项放在脚本路径之后即可。os.system 接受单个命令字符串,无法提供同样结构化的参数传递方式。

  5. 如何向脚本提供输入?
    对于简单、有限的一次交互,可以在 subprocess.run 中使用 input=...;需要更底层的进程控制时,可以使用 Popen,并设置 stdin=subprocess.PIPE,再调用 communicate(input=...)。communicate() 会发送输入,并在子进程结束后收集进程的输出流。