介绍
重复性任务的自动化时机已经成熟。开发人员和系统管理员通常会使用 shell 脚本自动执行例行任务,例如健康检查和文件备份。然而,随着这些任务变得越来越复杂,shell 脚本可能会变得更难维护。
幸运的是,我们可以使用 Python 而不是 shell 脚本来实现自动化。Python 提供了运行 shell 命令的方法,为我们提供了与这些 shell 脚本相同的功能,并得到了 Python 生态系统的额外支持。学习如何在 Python 中运行 shell 命令为我们以结构化和可扩展的方式自动执行计算机任务打开了大门。
在本文中,我们将研究在 Python 中执行 shell 命令的各种方法,以及使用每种方法的理想情况。
使用 os.system 运行命令
Python 允许我们使用该os.system()函数立即执行存储在字符串中的 shell 命令。让我们首先创建一个名为的新 Python 文件echo_adelle.py并输入以下内容:
import os
os.system("echo Hello from the other side!")
我们在 Python 文件中做的第一件事是导入os模块,其中包含system可以执行 shell 命令的函数。下一行正是这样做的,echo通过 Python 在我们的 shell 中运行命令。
在您的终端中,使用以下命令运行此文件,您应该会看到相应的输出:
$ python3 echo_adelle.py
Hello from the other side!
当echo命令打印到我们的stdout时,还会在我们的流中os.system()显示输出。stdout虽然在控制台中不可见,但该os.system()命令返回 shell 命令的退出代码。退出代码 0 表示它运行没有任何问题,任何其他数字表示错误。这样,您就可以控制命令流、检查结果并重试或在任何调用失败时返回错误:
# cd_return_codes.py
import os
home_dir = os.system("cd ~")
print("`cd ~` ran with exit code %d" % home_dir)
unknown_dir = os.system("cd doesnotexist")
print("`cd doesnotexis` ran with exit code %d" % unknown_dir)
在此脚本中,我们创建了两个变量,用于存储将目录更改为主文件夹和不存在的文件夹的执行命令的结果。运行这个文件,我们将看到:
$ python3 cd_return_codes.py
`cd ~` ran with exit code 0
sh: line 0: cd: doesnotexist: No such file or directory
`cd doesnotexist` ran with exit code 256
第一个将目录更改为主目录的命令成功执行。因此,os.system()返回它的退出代码,零,它存储在home_dir. 另一方面,unknown_dir存储失败的 bash 命令的退出代码,以将目录更改为不存在的文件夹。
该os.system()函数执行命令,将命令的任何输出打印到控制台,并返回命令的退出代码。如果我们想要在 Python 中对 shell 命令的输入和输出进行更细粒度的控制,我们应该使用该subprocess模块。
使用子进程运行命令
subprocess模块是 Python 推荐的执行 shell 命令的方式。它使我们能够灵活地抑制 shell 命令的输出或将各种命令的输入和输出链接在一起,同时仍然提供与os.system()基本用例类似的体验。
在一个名为 的新文件list_subprocess.py中,让我们对模块运行一个命令subprocess:
import subprocess
list_files = subprocess.run(["ls", "-l"])
print("The exit code was: %d" % list_files.returncode)
在第一行,我们导入subprocess模块,它是 Python 标准库的一部分。然后我们使用该subprocess.run()函数来执行命令。与 一样os.system(),该subprocess.run()命令返回已执行内容的退出代码。
与 不同os.system(),请注意如何subprocess.run()要求将字符串列表而不是单个字符串作为输入。列表的第一项是命令的名称。列表的其余项目是命令的标志和参数。
注意: 根据经验,您需要根据空格分隔参数,例如
ls -alhwould be ["ls", "-alh"], while ls -a -l -h, would be ["ls", "-a", -"l", "-h"]。再举一个例子,echo hello worldwould be ["echo", "hello", "world"],而echo "hello world"or echo hello\ worldwould be ["echo", "hello world"]。
运行此文件,您的控制台输出将类似于:
$ python3 list_subprocess.py
total 80
-rw-r--r--@ 1 stackabuse staff 216 Dec 6 10:29 cd_return_codes.py
-rw-r--r--@ 1 stackabuse staff 56 Dec 6 10:11 echo_adelle.py
-rw-r--r--@ 1 stackabuse staff 116 Dec 6 11:20 list_subprocess.py
The exit code was: 0
现在让我们尝试使用 的更高级功能之一subprocess.run(),即忽略输出到stdout。在同一list_subprocess.py文件中,更改:
list_files = subprocess.run(["ls", "-l"])
对此:
list_files = subprocess.run(["ls", "-l"], stdout=subprocess.DEVNULL)
命令的标准输出现在通过管道传输到特殊/dev/null设备,这意味着输出不会出现在我们的控制台上。在您的 shell 中执行该文件以查看以下输出:
$ python3 list_subprocess.py
The exit code was: 0
如果我们想为命令提供输入怎么办?subprocess.run()提供了参数input:
import subprocess
useless_cat_call = subprocess.run(["cat"],
stdout=subprocess.PIPE,
text=True,
input="Hello from the other side")
print(useless_cat_call.stdout) # Hello from the other side
我们使用subprocess.run()了很多命令,让我们来看看:
stdout=subprocess.PIPE告诉 Python 将命令的输出重定向到一个对象,以便稍后可以手动读取text=True返回stdout并stderr作为字符串。默认返回类型是字节。input="Hello from the other side"告诉 Python 添加字符串作为cat命令的输入。
运行它会产生以下输出:
Hello from the other side
我们也可以在Exception不手动检查返回值的情况下引发一个。在新文件中false_subprocess.py,添加以下代码:
import subprocess
failed_command = subprocess.run(["false"], check=True)
print("The exit code was: %d" % failed_command.returncode)
在您的终端中,运行此文件。您将看到以下错误:
$ python3 false_subprocess.py
Traceback (most recent call last):
File "false_subprocess.py", line 4, in <module>
failed_command = subprocess.run(["false"], check=True)
File "/usr/local/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/subprocess.py", line 512, in run
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['false']' returned non-zero exit status 1.
通过使用check=True,我们告诉 Python 在遇到错误时引发任何异常。由于我们确实遇到了错误,print所以最后一行的语句没有被执行。
该subprocess.run()函数为我们提供了os.system()执行 shell 命令时所没有的巨大灵活性。这个函数是subprocess.Popen类的简化抽象,它提供了我们可以探索的附加功能。
使用 Popen 运行命令
在subprocess.Popen与 shell 交互时,该类向开发人员公开了更多选项。但是,我们需要更明确地检索结果和错误。
默认情况下,subprocess.Popen如果 Python 程序的命令尚未完成执行,则不会停止对其的处理。在名为 的新文件list_popen.py中,键入以下内容:
import subprocess
list_dir = subprocess.Popen(["ls", "-l"])
list_dir.wait()
此代码等效于list_subprocess.py. 它使用 运行命令subprocess.Popen,并等待它完成,然后再执行 Python 脚本的其余部分。
假设我们不想等待我们的 shell 命令完成执行,以便程序可以处理其他事情。它如何知道 shell 命令何时完成执行?
如果命令已完成运行或仍在执行,该poll()方法将返回退出代码。None例如,如果我们想检查是否list_dir完成而不是等待它,我们将有以下代码行:
list_dir.poll()
要使用 管理输入和输出subprocess.Popen,我们需要使用communicate()方法。
在名为 的新文件cat_popen.py中,添加以下代码片段:
import subprocess
useless_cat_call = subprocess.Popen(["cat"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
output, errors = useless_cat_call.communicate(input="Hello from the other side!")
useless_cat_call.wait()
print(output)
print(errors)
该communicate()方法采用一个input参数,该参数用于将输入传递给 shell 命令。该communicate方法还返回stdout和stderr设置时间。
了解了背后的核心思想后subprocess.Popen,我们现在已经介绍了在 Python 中运行 shell 命令的三种方法。让我们重新检查它们的特性,以便我们知道哪种方法最适合项目的要求。
使用 Python 运行 Shell 命令的最佳方法?
如果您需要运行一个或几个简单的命令并且不介意它们的输出是否进入控制台,您可以使用os.system()命令。如果要管理 shell 命令的输入和输出,请使用subprocess.run(). 如果你想运行命令并在执行时继续做其他工作,请使用subprocess.Popen.
这是一个包含一些可用性差异的表格,您也可以使用这些差异来告知您的决定:
| os.system | subprocess.run | subprocess.Popen | |
|---|---|---|---|
| 需要解析的参数 | 否 | 是 | 是 |
| 等待命令 | 是 | 是 | 否 |
| 与标准输入和标准输出通信 | 否 | 是 | 是 |
| 返回 | 返回值 | 对象 | 对象 |
结论
Python 允许您执行 shell 命令,您可以使用这些命令启动其他程序或更好地管理用于自动化的 shell 脚本。根据我们的用例,我们可以使用os.system(),subprocess.run()或subprocess.Popen来运行 bash 命令。
使用这些技术,您准备使用 Python 运行什么外部任务呢?