📜  使用Python执行Shell命令

📅  最后修改于: 2020-08-28 04:37:39             🧑  作者: Mango

介绍

重复任务对于自动化已经成熟。对于开发人员和系统管理员来说,使用Shell脚本自动执行诸如运行状况检查和文件备份之类的常规任务是很常见的。但是,随着这些任务变得更加复杂,shell脚本可能变得难以维护。

幸运的是,我们可以使用Python代替shell脚本来实现自动化。Python提供了运行Shell命令的方法,为我们提供了与这些Shell脚本相同的功能。学习如何在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命令打印到我们的时stdoutos.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模块。

使用子流程运行命令

子模块是Python来执行shell命令推荐的方式。它使我们能够灵活地抑制shell命令的输出或将各种命令的输入和输出链接在一起,同时仍提供与os.system()基本用例相似的体验。

在名为的新文件中list_subprocess.py,编写以下代码: 

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 -alhbe ["ls", "-alh"],while ls -a -l -h,be ["ls", "-a", -"l", "-h"]。再举一个例子,echo hello world将是["echo", "hello", "world"],而echo "hello world"echo hello\ world将是["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设备,这意味着该输出不会出现在我们的控制台上。在您的外壳中执行文件以查看以下输出:

$ python3 list_subprocess.py
The exit code was: 0

如果我们想为命令提供输入怎么办?将subprocess.run()通过其促进这一input说法。创建一个名为的新文件cat_subprocess.py,输入以下内容:

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返回stdoutstderr作为字符串。默认的返回类型是字节。
  • 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 
    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与外壳进行交互时,该类向开发人员提供更多选项。但是,我们在获取结果和错误时需要更加明确。

默认情况下,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方法还返回stdoutstderr设置它们。

了解了背后的核心思想之后subprocess.Popen,我们现在介绍了三种在Python中运行shell命令的方法。让我们重新检查它们的特征,以便我们知道哪种方法最适合项目的需求。

我应该使用哪一个?

如果您需要运行一个或几个简单命令,并且不介意它们的输出是否进入控制台,则可以使用该os.system()命令。如果要管理shell命令的输入和输出,请使用subprocess.run()。如果要运行命令并在执行过程中继续执行其他工作,请使用subprocess.Popen

这是一个具有一些可用性差异的表,您还可以使用该表来告知您的决定:

这是一个具有一些可用性差异的表,您还可以使用该表来告知您的决定:

操作系统 subprocess.run 子进程
需要解析的参数 没有
等待命令 没有
与stdin和stdout通信 没有
return 返回值 目的 目的

结论

Python允许您执行Shell命令,您可以使用它们来启动其他程序或更好地管理用于自动化的Shell脚本。根据我们的使用情况下,我们可以使用os.system()subprocess.run()subprocess.Popen运行的bash命令。