跳至主要内容

Python subprocess 入门:基础与示例

通过我们的分步指南,学习如何使用 Python 的 subprocess 模块运行外部命令,并配有完整示例。
· 15分钟 阅读

使用 AI 探索

您可以使用 Python 的 subprocess 模块创建新进程、连接其输入和输出,并获取其返回码和/或输出。本文将带您了解 subprocess 模块的基础知识,包括如何运行外部命令、重定向输入和输出,以及处理错误。无论您是初学者还是有经验的 Python 开发者,本教程都将为您在项目中高效使用 subprocess 模块提供必要的知识。

什么是 Python Subprocess?

Python 的 subprocess 模块是一种工具,允许您在 Python 代码中运行其他程序或命令。它可用于打开新程序、向其发送数据并取回结果。

使用 Python 的 subprocess 模块,就像用 Python 向计算机下达指令,而不是在命令行中手动输入。该模块使任务自动化和将其他程序与您的 Python 代码集成变得容易。例如,您可以使用 subprocess 模块运行 ls 或 ping 这样的 shell 命令,并在 Python 代码中获取该命令的输出。您还可以用它来运行其他 Python 脚本或可执行文件,例如 Windows 上的 .exe 文件。

此外,subprocess 模块可以重定向进程的输入和输出,这意味着您可以控制发送到进程的数据以及从进程接收的数据。

subprocess 模块最有用的能力之一,是它使用户能够处理子进程生成的输入、输出和错误(通常在 stderr 上)。配合 check=True,会抛出 CalledProcessError,您可以检查 e.stdout/e.stderr。 这一特性被认为是该模块最强大的方面之一。得益于此,调用子进程的过程变得更强大且更灵活。例如,现在可以将子进程的输出作为变量,在整个 Python 脚本中使用。

何时使用 Python Subprocess

让我们看看在什么情况下应使用 Python 的 subprocess 模块。

自动化系统任务

subprocess 模块可用于自动化各种系统任务,例如运行备份、启动和停止服务,以及安排 cron 任务。例如,您可以使用 subprocess 模块运行 cp 命令来创建文件备份,或 systemctl(现代 Linux)或 service(较旧系统)来启动和停止服务。 您还可以使用 subprocess 模块调用调度工具(如 Linux 上的 cron 或 Windows 上的任务计划程序),但调度本身由这些工具处理,而不是由 subprocess 负责。

运行命令行工具

subprocess 模块可用于运行命令行工具,如 grep、sed 和 awk,并在您的 Python 代码中处理其输出。例如,您可以使用 subprocess 模块运行 grep 命令,在文件中搜索特定模式,然后在 Python 代码中处理输出。这对于日志分析、数据处理和文本处理等任务很有用。

运行外部可执行文件

subprocess 模块可以运行其他可执行文件,例如 Windows 上的 .exe 文件,并控制其行为。例如,您可以使用 subprocess 模块运行一个执行特定任务的可执行文件,然后在自己的代码中使用该可执行文件的输出。这对于图像处理、数据分析和机器学习任务很有帮助。

将脚本作为后台进程运行

您可以使用 subprocess 模块将脚本作为后台进程运行,使其在主程序退出后继续运行。例如,您可以使用 subprocess 模块运行一个执行特定任务的脚本,并在不等待脚本完成的情况下退出主程序。这对于监控、日志记录和数据收集很有用。

使用非 Python 解释器运行脚本

subprocess 模块可以帮助您运行用其他语言(如 Perl、Ruby 和 Bash)编写的脚本。例如,您可以使用 subprocess 模块运行一个执行特定任务的 Perl 脚本,然后在自己的代码中使用该脚本的输出。这对于数据处理、文本处理和系统管理非常有用。

并行处理

subprocess 模块可用于并行运行多个进程,这对图像处理、数据分析和机器学习等任务非常有用。例如,您可以使用 subprocess 模块运行同一脚本的多个实例,每个实例处理数据的不同部分,然后合并结果。

在线运行和编辑此教程中的代码

运行代码

Python Subprocess 示例 

现在我们来看一些 Python subprocess 的示例。

python subprocess run

subprocess.run() 方法是运行子进程并等待其完成的便捷方式。它允许您选择要运行的命令,并添加参数、环境变量以及输入/输出重定向等选项。子进程启动后,run() 方法会阻塞,直到子进程完成,并返回一个 CompletedProcess 对象,其中包含子进程的返回码和输出。

subprocess.run() 方法接收多个参数,其中一些为:

  • args:要运行的命令及其参数,以字符串列表的形式传入。

  • capture_output:设为 True 时,将捕获标准输出和标准错误。

  • text:设为 True 时,stdout 和 stderr 将以字符串返回,否则为字节。

  • check:布尔值,表示是否检查子进程的返回码;如果为 True 且返回码非零,则会抛出 CalledProcessError。

  • timeout:以秒为单位,指定在超时前等待子进程完成的时长。

  • shell:布尔值,指示是否在 shell 中运行命令。这意味着命令将作为字符串传入,并可使用 shell 特性,如通配符展开和变量替换。

subprocess.run() 方法还会返回一个 CompletedProcess 对象,包含以下属性:

  • args:已运行的命令及其参数。

  • returncode:子进程的返回码。

  • stdout:子进程的标准输出(当 text=True 时为字符串,否则为字节)。

  • stderr:子进程的标准错误(当 text=True 时为字符串,否则为字节)。

示例 1:运行 shell 命令:

import subprocess

result = subprocess.run("dir", shell=True, capture_output=True, text=True)

print(result.stdout)

输出: 

Volume in drive C has no label.

Volume Serial Number is E414-A41C

 Directory of C:\Users\owner

01/25/2023  10:56 AM    <DIR>          .

01/25/2023  10:56 AM    <DIR>          ..

07/19/2021  01:19 PM    <DIR>          .anaconda

07/19/2021  01:19 PM    <DIR>          .astropy

07/19/2021  01:19 PM    <DIR>          .aws

09/12/2022  08:48 AM               496 .bash_history

03/27/2022  03:08 PM    <DIR>          .cache

09/26/2021  06:58 AM    <DIR>          .conda

09/26/2021  06:59 AM                25 .condarc

…

在 Linux/macOS 上,请使用:result = subprocess.run(["ls", "-la"], capture_output=True, text=True)(无需 shell 参数)。

示例 2:运行 Python 脚本:

您也可以使用 subprocess.run() 方法运行 Python 脚本。我们先在 .py 文件中创建一个简单的 Python 脚本

print(“This is the output from subprocess module”)

将此文件保存为 file_donot_exist.py。现在,您可以使用 subprocess 模块来运行该文件:

import subprocess
import sys

result = subprocess.run(["python", "file_donot_exist.py"], capture_output=True, text=True, check=False)

print(result.stdout)

输出:

This is the output from subprocess module

示例 3:直接从函数运行 Python 代码:

对于简单用例,您可以在 subprocess.run() 函数中直接传入一条 Python 命令。如下所示:

result = subprocess.run([sys.executable, "-c", "print('This is directly from a subprocess.run() function')"], capture_output = True, text = True)

print(result.stdout)

输出:

This is directly from a subprocess.run() function

在 args 列表中,第一个元素 sys.executable 会动态解析为当前 Python 解释器的路径。这可确保与正在运行的进程一致,并避免使用诸如 "C:/..." 之类的硬编码路径。第二个元素 "-c" 表示将后续字符串作为 Python 代码而非脚本运行。请务必先导入 sys 以访问 sys.executable。

示例 4:使用 check 参数

check 参数是 Python subprocess 模块中 subprocess.run() 函数的可选参数。它是一个布尔值,用于控制函数是否应检查所运行命令的返回码。

当 check 设为 True 时,函数将检查命令的返回码,并在返回码非零时抛出 CalledProcessError 异常。该异常包含返回码、stdout、stderr 和命令等属性。

当 check 设为 False(默认)时,即使命令失败,函数也不会抛出异常——您需要自行检查返回码。

import subprocess

result = subprocess.run(["python", "file_donot_exist.py"], capture_output=True, text=True, check=True)

print(result.stdout)

print(result.stderr)

输出:

---------------------------------------------------------------------------

CalledProcessError                        Traceback (most recent call last)

<ipython-input-81-503b60184db8> in <module>

      1 import subprocess

----> 2 result = subprocess.run(["python", "file_donot_exist.py"], capture_output=True, text=True, check=True)

      3 print(result.stdout)

      4 print(result.stderr)

~\anaconda3\lib\subprocess.py in run(input, capture_output, timeout, check, *popenargs, **kwargs)

    514         retcode = process.poll()

    515         if check and retcode:

--> 516             raise CalledProcessError(retcode, process.args,

    517                                      output=stdout, stderr=stderr)

    518     return CompletedProcess(process.args, retcode, stdout, stderr)

CalledProcessError: Command '['python', 'file_donot_exist.py']' returned non-zero exit status 2.

请注意,该命令失败是因为 my_python_file2.py 不存在,且由于传入了 check=True,因此抛出了 CalledProcessError。如果设置为 check=False(或完全省略),进程将不会抛出异常;相反,您会在 stderr 中看到错误消息,并可自行决定如何处理。

import subprocess

result = subprocess.run(["python", "my_python_file2.py"], capture_output=True, text=True, check=False)

print(result.stdout)

print(result.stderr)

输出:

python: can't open file 'my_python_file2.py': [Errno 2] No such file or directory

python subprocess Popen

subprocess.Popen() 是一个更底层的运行子进程的接口,而 subprocess.run 是围绕 Popen 的更高层封装,旨在更便捷地使用。

Popen 允许您启动新进程并与其标准输入、输出和错误流交互。它返回一个正在运行的进程句柄,可用于等待进程完成、检查其返回码或终止它。

run() 是更方便的函数,可在一次调用中运行命令并捕获其输出,而无需您自己创建 Popen 对象并管理各个流。它还允许您为运行命令指定各种选项,例如命令失败时是否抛出异常。

一般来说,如果您只需运行命令并捕获其输出,应使用 run();如果需要对进程有更多控制,例如与其输入和输出流交互,则使用 Popen。

Popen 类接受与 run() 相同的参数,包括指定要运行命令的 args 以及 stdin、stdout、stderr、shell、cwd 和 env 等其他可选参数。此外,Popen 类还提供多个方法以便与进程交互,例如 communicate()、poll()、wait()、terminate() 和 kill()。

import subprocess

p = subprocess.Popen(["python", "--help"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)

output, errors = p.communicate()

print(output)

输出:

usage: python [option] ... [-c cmd | -m mod | file | -] [arg] ...

Options and arguments (and corresponding environment variables):

-b     : issue warnings about str(bytes_instance), str(bytearray_instance)

         and comparing bytes/bytearray with str. (-bb: issue errors)

-B     : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x

-c cmd : program passed in as string (terminates option list)

-d     : debug output from parser; also PYTHONDEBUG=x

-E     : ignore PYTHON* environment variables (such as PYTHONPATH)

…

这将运行命令 python –help 并创建一个新的 Popen 对象,存储在变量 p 中。通过 communicate() 方法捕获命令的标准输出和错误,分别存储在 output 和 errors 变量中。

subprocess.Popen 在您需要更多对进程的控制时很有用,例如向其发送输入、接收其输出或等待其完成。

python subprocess call

subprocess.call() 是 Python subprocess 模块中的一个函数,用于在单独的进程中运行命令并等待其完成。它返回命令的返回码:成功为 0,失败为非零。

call() 函数接受与 run() 相同的参数,包括指定要运行命令的 args,以及 stdin、stdout、stderr、shell、cwd 和 env 等其他可选参数。

除非通过 stdout 和 stderr 参数重定向,否则命令的标准输出和标准错误将发送到与父进程相同的 stdout 和 stderr。

import sys
import subprocess

return_code = subprocess.call([sys.executable, "--version"])

if return_code == 0:
    print("Command executed successfully.")
else:
    print("Command failed with return code", return_code)

输出:

Command executed successfully.

这将在单独的进程中运行命令 python -–version 并等待其完成。命令的返回码将存储在 return_code 变量中:成功为 0,失败为非零。

subprocess.call() 适用于您想运行命令并检查返回码,但不需要捕获输出的场景。

python subprocess check_output

check_output 是 subprocess 模块中的一个函数,与 run() 类似,但它仅返回命令的标准输出,并在返回码非零时抛出 CalledProcessError 异常。

函数 check_output() 接受与 run() 相同的参数,包括 args(要运行的命令)以及 stdin、stderr、shell、cwd 和 env 等可选参数。它以字节对象或字符串(若设定 text=True)形式返回命令的标准输出。若命令失败(退出状态非零),则抛出 CalledProcessError。

注意: 默认情况下,check_output() 仅捕获 stdout。如需在输出中包含 stderr,请显式使用 stderr=subprocess.STDOUT 重定向。

import subprocess
import sys

try:

    output = subprocess.check_output([sys.executable, "--version"], text=True)

    print(output)

except subprocess.CalledProcessError as e:

    print(f"Command failed with return code {e.returncode}")

输出:

Python 3.8.8

Python Subprocess 管道(Pipe)

Python 的 subprocess 模块提供了一种创建并与子进程交互的方式,可用于运行其他程序或命令。该模块的特性之一是能够创建管道(pipe),用于在父进程和子进程之间进行通信。

管道是一种单向通信通道,将一个进程的标准输出连接到另一个进程的标准输入。通过管道,可以将一个命令的输出连接到另一个命令的输入,使第一个命令的输出作为第二个命令的输入。

可以在使用 Popen 类时,将 stdout 或 stdin 参数指定为 subprocess.PIPE 来创建管道。

例如,以下代码创建了一个管道,将 ls 命令的输出连接到 grep 命令的输入,后者会过滤输出,仅显示包含单词 "file" 的行:

import subprocess


ls_process = subprocess.Popen(["ls"], stdout=subprocess.PIPE, text=True)
grep_process = subprocess.Popen(
    ["grep", "sample"],
    stdin=ls_process.stdout,
    stdout=subprocess.PIPE,
    text=True
)
ls_process.stdout.close()  # POSIX-only: prevents deadlocks by closing the pipe
output, error = grep_process.communicate()


print(output)
print(error)

输出:

sample_data

在此示例中,使用 Popen 类创建了两个子进程:一个用于 ls 命令,另一个用于 grep 命令。通过 subprocess.PIPE 将 ls 命令的 stdout 连接到 grep 命令的 stdin,从而在两个进程之间创建了一个管道。使用 communicate() 方法将 ls 命令的输出发送给 grep 命令,并取回过滤后的输出。

结论

Python 的 subprocess 模块提供了一种功能强大且灵活的方式来创建并与子进程交互,使您能够在 Python 脚本中运行其他程序或发出命令。从简单的 subprocess.call() 到更高级的特性,如管道、输入输出重定向以及传递环境变量,subprocess 模块几乎为每种用例都提供了支持。它是自动化重复性任务、运行系统命令,甚至与其他编程语言和平台交互的绝佳方式。

在使用 subprocess 模块时,务必要记住,运行外部命令会带来安全风险,尤其是在使用 shell=True 参数或传入未清洗的输入时。通常建议使用 subprocess.run() 函数,并根据需要指定命令的运行选项,例如命令失败时是否抛出异常。 

如果您有兴趣深入探索通过 Python 进行命令行自动化的无限可能,请查看我们的 Python 课程 Command Line Automation。在本课程中,您将学习编写自动化代码,浏览文件系统、查找符合特定模式的文件,并在众多情形下判断文件是否重复。完成课程后,您将能够管理和交互 Unix 进程,并自动化各种常见的文件系统活动。 

使用 DataCamp for Business 提升您团队的 Python 技能

如果您或您的团队希望提升 Python 和命令行自动化技能,欢迎了解 DataCamp for Business。DataCamp 为各种规模的团队提供定制化学习方案,助力企业在快速发展的技术领域保持领先。通过 DataCamp for Business,您可以利用课程和自定义学习路径为团队打造 Python、自动化及其他关键数据科学工具的专业能力。无论您是初创公司还是大型企业,DataCamp for Business 都能提供实现团队学习目标所需的资源与灵活性。立即申请演示以了解更多。 

Python Subprocess 常见问题

subprocess.call() 和 subprocess.run() 有何区别?

subprocess.call() 是一个运行命令并等待其完成的函数,返回该命令的返回码。subprocess.run() 则更强大,允许您运行命令、捕获其输出,并为命令的运行方式指定各种选项,例如命令失败时是否抛出异常。

如何在 Python 中使用 subprocess 运行命令并捕获其输出?

您可以使用 subprocess.run() 函数在一次调用中运行命令并捕获其输出。例如,以下代码运行 ls 命令,并将其输出捕获到 result 变量中:

import subprocess

result = subprocess.run(["ls"], stdout=subprocess.PIPE)

print(result.stdout.decode())
如何将变量作为参数传递给子进程中的命令?

您可以将变量作为参数传递给子进程中的命令,只需把它们放入传给 subprocess.run() 或 subprocess.Popen() 的列表中即可。例如,以下代码运行 echo 命令,并将变量 message 的值作为参数传入:

import subprocess

message = "Hello, World!"

subprocess.run(["echo", message])
如何使用 subprocess 将命令的输出重定向到文件?

您可以在命令中使用 > 运算符将命令的输出重定向到文件。例如,以下代码运行 ls 命令,并将其输出重定向到名为 ls.txt 的文件:

import subprocess

subprocess.run("ls > ls.txt", shell=True)
如何使用 subprocess 在后台运行命令?

您可以在使用 subprocess.Popen() 创建新进程时,将 start_new_session 参数设置为 True,以在后台运行命令。这将创建一个新的进程组,使您可以在后台运行该命令。

import subprocess

subprocess.Popen(["ls"], start_new_session=True)
如何检查使用 subprocess 运行的命令的返回码?

您可以通过访问 subprocess.run() 返回的 subprocess.CompletedProcess 对象的 returncode 属性来检查命令的返回码。例如,以下代码运行 ls 命令并检查其返回码:

import subprocess

result = subprocess.run(["ls"])

if result.returncode == 0:

    print("Command ran successfully")

else:

    print("Command failed with error code", result.returncode)
如何向使用 subprocess 运行的命令传递环境变量?

您可以通过将环境变量放入字典中,并在使用 subprocess.Popen() 或 subprocess.run() 创建新进程时,将该字典作为 env 参数传入,从而向命令传递环境变量。例如,以下代码设置环境变量 TEST_VAR 并运行 printenv 命令:

import subprocess

env = {'TEST_VAR': 'test_value'}

subprocess.run(["printenv", "TEST_VAR"], env=env)
Moez Ali

作者

Moez Ali

数据科学家,PyCaret 的创始人兼作者

主题