Python子模块

时间:2020-02-23 14:43:33  来源:igfitidea点击:

对于希望自动执行特定操作系统任务或者在脚本中执行一些命令的系统管理员而言,运行和产生新的系统进程可能会很有用。 Python提供了许多库来调用外部系统实用程序,并且它与生成的数据进行交互。在本教程中,我们将学习一个这样的pythonsubprocess()模块。

创建的第一个库是OS模块,它提供了一些有用的工具来调用外部进程,例如os.system,os.spwan和os.popen *。它缺少一些基本功能,因此Python开发人员引入了subprocess模块,该模块旨在替换诸如os.system()os.spawnv()popen()的变体之类的功能。在" os"," popen2"模块和" commands"模块中。

python子流程模块的内容

访问pythonsubprocess()模块的内容

#!/usr/bin/env python3
import subprocess
# Access contents of subprocess module
print(dir(subprocess))

该脚本的输出:

# python3 exec_system_commands.py
['CalledProcessError', 'CompletedProcess', 'DEVNULL', 'PIPE', 'Popen', 'STDOUT', 'SubprocessError', 'TimeoutExpired', '_PIPE_BUF', '_PLATFORM_DEFAULT_CLOSE_FDS', '_PopenSelector', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_active', '_args_from_interpreter_flags', '_cleanup', '_mswindows', '_optim_args_from_interpreter_flags', '_posixsubprocess', '_time', 'builtins', 'call', 'check_call', 'check_output', 'errno', 'getoutput', 'getstatusoutput', 'io', 'list2cmdline', 'os', 'run', 'select', 'selectors', 'signal', 'sys', 'threading', 'time', 'warnings']

使用python subprocess.Popen()函数

  • Python程序可以使用内置的subprocess模块中的Popen()函数来启动计算机上的其他程序。

  • 以Popen()函数名称命名的P代表process。

  • 如果我们打开了一个应用程序的多个实例,则这些实例中的每一个都是同一程序的单独进程。

  • 例如,如果我们同时打开Web浏览器的多个窗口,则每个窗口都是Web浏览器程序的不同过程

论点意义
args字符串或者程序参数序列。
bufsize在创建“stdin/stdout/stderr”管道文件对象时,它作为“open()”函数的缓冲参数提供。
可执行程序要执行的替换程序。
stdin、stdout、stderr它们分别指定执行程序的标准输入、标准输出和标准错误文件句柄。
shell如果'True',命令将通过shell执行(默认值为'False')。在Linux中,这意味着在运行子进程之前调用/bin/sh
cwd在执行子目录之前设置当前目录。
env定义新流程的环境变量。

使用subprocess.Popen的通用syntax

subprocess.Popen(cmd,shell=True/False,stdout=subprocess.PIPE,stderr=subprocess.PIPE)

在这种语法中,我们将命令输出(stdout)和命令错误(stderr)存储在相同的变量即sp中。

这是一个非常基本的示例,其中我们使用python子进程执行ls -ltr,类似于在shell终端上执行ls -ltr的方式

#!/usr/bin/env python3
import subprocess
# Define command as string
cmd = 'ls -ltr'
# Use shell to execute the command and store it in sp variable
sp = subprocess.Popen(cmd,shell=True)
# Store the return code in rc variable
rc=sp.wait()
# Print the content of sp variable
print(sp)

该脚本的输出:

# python3 exec_system_commands.py
total 308256
-rw-r--r--. 1 root root 315632268 Jan  1  2017 large_file
-rw-r--r--. 1 root root       577 Apr  1 00:00 my-own-rsa-key.pub
-rw-r--r--. 1 root root      2610 Apr  1 00:00 my-own-rsa-key
-rwxr--r--  1 root root       428 Jun  8 22:04 create_enum.py
-rwxr--r--  1 root root       176 Jun 11 06:33 check_string.py
-rw-r--r--  1 root root       525 May 11 19:29 exec_system_commands.py
<subprocess.Popen object at 0x7ff99a7842e8>

"我们是否观察到"最后一行<位于0x7ff99a7842e8的<subprocess.Popen对象>,这是因为我们没有存储系统命令的输出,而是将其打印在控制台上。

使用python subprocess.communicate()读取stdin,stdout和stderr

subprocess中,Popen()可以与三个通道交互,并将每个流重定向到一个外部文件,或者重定向到一个名为PIPE的特殊值。另一种名为" communicate()"的方法用于从" stdout"读取并在" stdin"上写入。

产生的进程可以通过三个渠道与操作系统进行通信:

  • 标准输入(stdin)

  • 标准输出(stdout)

  • 标准错误(stderr)

communicate()方法可以从用户处获取输入,并返回标准输出和标准错误,如以下代码片段所示:

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ping -c 5 google.com'
# Use shell to execute the command, store the stdout and stderr in sp variable
sp = subprocess.Popen(cmd,
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE)
# Store the return code in rc variable
rc=sp.wait()
# Separate the output and error by communicating with sp variable.
# This is similar to Tuple where we store two values to two different variables
out,err=sp.communicate()
print('Return Code:',rc,'\n')
print('output is: \n', out)
print('error is: \n', err)

在这段代码中,如果我们观察到我们将STDOUT和STDERR存储到sp变量中,然后使用communication()方法将output和error分别分成两个不同的变量

该脚本的输出:

# python3 exec_system_commands.py
Return Code: 0
output is:
 b b'PING google.com (142.250.67.142) 56(84) bytes of data.\n64 bytes from bom12s06-in-f14.1e100.net (142.250.67.142): icmp_seq=1 ttl=115 time=70.8 ms\n64 bytes from bom12s06-in-f14.1e100.net (142.250.67.142): icmp_seq=2 ttl=115 time=108 ms\n64 bytes from bom12s06-in-f14.1e100.net (142.250.67.142): icmp_seq=3 ttl=115 time=103 ms\n64 bytes from bom12s06-in-f14.1e100.net (142.250.67.142): icmp_seq=4 ttl=115 time=106 ms\n64 bytes from bom12s06-in-f14.1e100.net (142.250.67.142): icmp_seq=5 ttl=115 time=71.4 ms\n\n--- google.com ping statistics ---\n5 packets transmitted, 5 received, 0% packet loss, time 24ms\nrtt min/avg/max/mdev = 70.791/91.788/107.585/16.948 ms\n'
error is:
 b''
  • 如我们所见,返回码为" 0",这表示命令已成功执行

  • 但是输出不清楚,因为默认情况下,文件对象是在"二进制模式"下打开的。在输出开始处观察" b",这意味着"输出以字节码表示",我们将在稍后进行介绍。

  • 错误代码也为空,这再次是因为我们的命令成功。

将字节转换为字符串

我们可以使用几种方法将字节转换为subprocess.Popen输出的字符串格式:

  • 解码(" utf-8")

  • Universal_Newlines

  • 文本(Python 3.7+支持此功能,已将文本添加为universal_newlines的可读性更高的别名)

我们将在脚本中使用universal_newlines = True

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ping -c 5 google.com'
# Use shell to execute the command
sp = subprocess.Popen(cmd,
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True)
# Separate the output and error.
# This is similar to Tuple where we store two values to two different variables
out,err=sp.communicate()
# Store the return code in rc variable
rc=sp.wait()
print('Return Code:',rc,'\n')
print('output is: \n', out)
print('error is: \n', err)

现在脚本输出更具可读性:

# python3 exec_system_commands.py
Return Code: 0
output is:
 PING google.com (172.217.26.238) 56(84) bytes of data.
64 bytes from bom05s09-in-f14.1e100.net (172.217.26.238): icmp_seq=1 ttl=115 time=90.8 ms
64 bytes from bom05s09-in-f14.1e100.net (172.217.26.238): icmp_seq=2 ttl=115 time=89.9 ms
64 bytes from bom05s09-in-f14.1e100.net (172.217.26.238): icmp_seq=3 ttl=115 time=79.10 ms
64 bytes from bom05s09-in-f14.1e100.net (172.217.26.238): icmp_seq=4 ttl=115 time=127 ms
64 bytes from bom05s09-in-f14.1e100.net (172.217.26.238): icmp_seq=5 ttl=115 time=127 ms
--- google.com ping statistics --
5 packets transmitted, 5 received, 0% packet loss, time 170ms
rtt min/avg/max/mdev = 79.954/103.012/127.346/20.123 ms
error is:

shell = True或者shell = False之间的区别是使用哪个?

在shell = True中使用subprocess.Popen

  • 每当我们使用shell = True时,Python都会打开一个新的Shell来执行提供的命令。

  • 使用此命令的好处是我们可以以纯文本格式给出命令,而Python将以提供的格式执行该命令。

  • 这对于Linux系统命令很有用

说明:

subprocess使用的默认shell是/bin/sh。如果我们正在使用其他shell程序,例如tch或者csh,则可以在可执行参数中对其进行定义。

在这段python代码中,我只是尝试使用ls -lrt和shell = True列出当前目录的内容。

#!/usr/bin/env python3
import subprocess
cmd = "ls -lrt"
# Use shell to execute the command
sp = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)
# Store the return code in rc variable
rc=sp.wait()
# Separate the output and error.
# This is similar to Tuple where we store two values to two different variables
out,err=sp.communicate()
print('Return Code: ',rc)
print('output is: \n', out)
print('error is: \n', err)

现在,如果我们执行脚本:

# python3 exec_system_commands.py
Return Code:  0
output is:
 total 308256
-rw-r--r--. 1 root root 315632268 Jan  1  2017 large_file
-rw-r--r--. 1 root root       577 Apr  1 00:00 my-own-rsa-key.pub
-rw-r--r--. 1 root root      2610 Apr  1 00:00 my-own-rsa-key
-rwxr--r--  1 root root       428 Jun  8 22:04 create_enum.py
-rwxr--r--  1 root root       176 Jun 11 06:33 check_string.py
-rw-r--r--  1 root root       475 May 11 16:52 exec_system_commands.py
error is:

输出类似于从shell终端手动执行ls -lrt时获得的输出。现在,让我通过给出一些错误的命令来"有意使此脚本失败",然后从脚本中输出:

# python3 exec_system_commands.py
Return Code:  127
output is:
 b''
error is:
 b'/bin/sh: lsss: command not found\n'
  • 在我们的脚本中,我将" ls"更改为" lsss"以故意使脚本失败。

  • 现在,该脚本在输出为以下时有一个空输出:

  • 返回码非零,表示错误

  • 当错误包含所提供命令的错误输出时

在shell = False的情况下使用subprocess.Popen

在本节中,我们将使用shell = False与pythonsubprocess.Popen来了解与shell = True的区别,因此,如果我们定义shell = True,则要求Python在新的shell下执行命令但是使用shell = False时,我们必须以列表格式而不是前面的字符串格式提供命令。

因此,例如,我们在字符串下面使用了" shell = True"

cmd = 'ls -lrt'

现在要能够将此命令与shell = False一起使用,我们必须将其转换为List格式,这可以手动完成:

cmd = ['ls', '-lrt']

或者,如果对我们来说太复杂,请使用" split()"方法(我有点懒),该方法应将" string"转换为" list"

cmd = 'ls -lrt'.split()

这应该将命令转换为字符串,可以与shell = False一起使用

因此,让我们以相同的示例为例,并将命令转换为" list"格式,以便能够与Python的" subprocess"和" shell = False"配合使用。

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ls -ltr'.split()
# Check the list value of cmd
print('command in list format:',cmd,'\n')
# Use shell=False to execute the command
sp = subprocess.Popen(cmd,shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)
# Store the return code in rc variable
rc=sp.wait()
# Separate the output and error.
# This is similar to Tuple where we store two values to two different variables
out,err=sp.communicate()
print('Return Code:',rc,'\n')
print('output is: \n', out)
print('error is: \n', err)

该脚本的输出:

# python3 exec_system_commands.py
command in list format: ['ls', '-ltr']
Return Code: 0
output is:
 total 308256
-rw-r--r--. 1 root root 315632268 Jan  1  2017 large_file
-rw-r--r--. 1 root root       577 Apr  1 00:00 my-own-rsa-key.pub
-rw-r--r--. 1 root root      2610 Apr  1 00:00 my-own-rsa-key
-rwxr--r--  1 root root       428 Jun  8 22:04 create_enum.py
-rwxr--r--  1 root root       176 Jun 11 06:33 check_string.py
-rw-r--r--  1 root root       623 May 11 17:10 exec_system_commands.py
error is:

什么时候应该使用shell = True或者shell = False?

现在我们一定在想,我什么时候应该使用哪种方法?最终,两者似乎都在做相同的事情,一种或者另一种方式。在下一个示例中,我们将理解这一点。

在此示例脚本中,我们将尝试使用带有shell = True的系统环境变量。

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'echo $PATH'
# Check the list value of cmd
print('command in list format:',cmd,'\n')
# Use shell to execute the command
sp = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)
# Store the return code in rc variable
rc=sp.wait()
# Separate the output and error.
# This is similar to Tuple where we store two values to two different variables
out,err=sp.communicate()
print('Return Code:',rc,'\n')
print('output is: \n', out)
print('error is: \n', err)

该脚本的输出符合预期,它正在打印我们的PATH变量内容

# python3 exec_system_commands.py
command in list format: echo $PATH
Return Code: 0
output is:
 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
error is:

现在让我们尝试使用shell = False来获得相同的结果。

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'echo $PATH'.split()
# Check the list value of cmd
print('command in list format:',cmd,'\n')
# Use shell=False to execute the command
sp = subprocess.Popen(cmd,shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)
# Store the return code in rc variable
rc=sp.wait()
# Separate the output and error.
# This is similar to Tuple where we store two values to two different variables
out,err=sp.communicate()
print('Return Code:',rc,'\n')
print('output is: \n', out)
print('error is: \n', err)

其中脚本输出将只将$PATH变量打印为字符串,而不是$PATH变量的内容。

# python3 exec_system_commands.py
command in list format: ['echo', '$PATH']
Return Code: 0
output is:
 $PATH
error is:
  • 因此,无论何时计划使用任何"系统变量",都必须使用" shell = True",因为" shell = False"将无法解释系统变量,而会将其视为另一个字符串。

  • shell = True方法的缺点之一是,每次使用该方法时,Python都需要打开一个新的Shell终端来执行命令,获取状态,然后关闭终端,这会花费一些时间使用shell = False,我们可以获得相对更快的结果。

重要的提示:

在某些特定情况下,强烈建议不要使用" shell = True"

"那我们学到了什么?总结一下:

  • 如果shell = True,那么命令将被视为字符串(类似于os命令)

  • 如果shell = False,那么命令将被视为"列表"。

  • 要使用shell = False转换字符串,可以使用split()方法来简化操作

  • shell = False无法识别OS系统环境变量

实际例子

让我们从实时场景中举一个实际的例子。在此python脚本中,我们旨在获取失败服务的列表。在RHEL 7/8中,我们使用systemctl --failed获取失败服务的列表

# systemctl --failed
  UNIT               LOAD   ACTIVE SUB    DESCRIPTION
● kdump.service      loaded failed failed Crash recovery kernel arming
● nfs-server.service loaded failed failed NFS server and services
LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.
2 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.

现在,我只希望获得服务名称,而不是完整的输出。

说明:

如果我们只需要执行系统命令,则可以使用os.system(your_command),但是其中由于我们要操纵输出,因此我们使用pythonsubprocess

以下是我们的示例脚本:

1 #!/usr/bin/env python3
  2
  3 import subprocess
  4
  5 # Define command as string and then split() into list format
  6 cmd = 'systemctl --failed'.split()
  7
  8 # Check the list value of cmd
  9 print('command in list format:',cmd)
 10
 11 # Use shell=False to execute the command
 12 sp = subprocess.Popen(cmd,shell=False,stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True)
 13
 14 # Store the return code in rc variable
 15 rc=sp.wait()
 16
 17 # Separate the output and error by communicating with sp variable.
 18 # This is similar to Tuple where we store two values to two different variables
 19 out,err=sp.communicate()
 20
 21 if rc == 0:
 22     for line in out.splitlines():
 23         if "failed" in line:
 24             print(line.split()[1])
 25 else:
 26     print('The command returned an error: ',err)

其中第3行:我们导入了subprocess模块第6行:我们定义了命令变量,并使用split()将其用作List。
第9行: list格式,只是为了确保split()能够按预期工作'
第12行:subprocess.Popen命令以shell = False执行该命令。将输出和错误都存储到同一变量中。用换行符char返回输出,而不用字节码
第15行返回输出:这里可能不需要,但是使用wait()是一种好习惯,因为有时子进程可能需要一些时间来执行命令例如某些SSH进程,在这种情况下," wait()"将确保成功执行" subprocess"命令,并将返回代码存储在wait()中。
第19行:我们需要" communicate()"来获取从subprocess.Popen输出并输出错误值并将其分别存储在out和err变量中
第21行:如果返回码为0,即命令执行成功,则在第22行中:使用for循环和在每一行上运行。其中我们将使用pythonsplitlines()方法,该方法根据行拆分字符串。它在行边界处中断字符串,并返回分割后的字符串列表。
第23行:使用in运算符在第24行中搜索失败的字符串:如果在第25行中发现失败,则将找到的行拆分为列表,然后打印带有1个索引的字符串的内容数字"第25行:",如果命令无法执行"
第26行:",则从我们使用" communicate()"存储的" err"变量中打印出提供的错误消息。

该脚本的输出:

# python3 exec_system_commands.py
command in list format: ['systemctl', '--failed']
kdump.service
nfs-server.service

因此,我们只能使用pythonsubprocess模块来打印失败的服务

使用python subprocess.call()函数

  • subprocess模块提供了另一个功能,该功能使生成的进程比使用Popen()更安全。

  • subprocess.call()函数等待被调用的命令/程序完成对输出的读取。

  • 它支持与Popen()构造函数相同的参数,例如shell,可执行文件和cwd,但是这次,脚本将等待程序完成并填充返回代码,而无需communicate( )

  • 我们可以使用Popen.returncode获取退出状态。

  • 要禁止显示" stdout"或者" stderr",请提供" subprocess.DEVNULL"值,该值指示将使用特殊文件" os.devnull"。

说明:

请勿将" stdout = PIPE"或者" stderr = PIPE"与该功能配合使用,因为这可能会基于子进程的输出量而死锁。需要管道时,将Popencommunicate()方法一起使用。

  • 在此示例python代码中,我们将使用ip link show eth0检查eth0接口的可用性。

  • 命令的输出将存储在/tmp/dataFile中,如果eth0可用,我们打印是,此服务器上的eth0可用

  • 同样对于错误情况,ip链接show eth0的输出将被写入/tmp/dataFile中,我们可以选择使用另一个文件进行错误输出。

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ip link show eth0'.split()
# Check the list value of cmd
print('command in list format:',cmd)
# Open the /tmp/dataFile and use "w" to write into the file
myfile = open("/tmp/dataFile", "w")
# Use shell=False to execute the command
is_eth0_present = subprocess.call(cmd, shell=False, stdout=myfile, stderr=subprocess.STDOUT)
if is_eth0_present == 0:
    print('Yes, eth0 is available on this server')
else:
    print('No, eth0 is not available on this server')
myfile.close()

该脚本的输出(当eth0可用时)

# python3 exec_system_commands.py
command in list format: ['ip', 'link', 'show', 'eth0']
Yes, eth0 is available on this server

/tmp/dataFile的内容

# cat /tmp/dataFile
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:5a:d3:83 brd ff:ff:ff:ff:ff:ff

该脚本的输出(当eth0不可用时)

# python3 exec_system_commands.py
command in list format: ['ip', 'link', 'show', 'eth0']
No, eth0 is not available on this server

/tmp/dataFile的内容

# cat /tmp/dataFile
Device "eth0" does not exist.

使用python subprocess.check_call()函数

  • 这是subprocess模块的一部分,可以运行带有参数的命令。

  • check_call将等待命令执行完成。

  • 如果执行成功,则函数将返回零,然后返回,否则引发CalledProcessError

  • " CalledProcessError"对象将在" returncode"属性中具有返回码。

我将尝试使用subprocess.check_now来打印命令执行输出:

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ping -c2 google.com'.split()
# Check the list value of cmd
print('command in list format:',cmd)
sp = subprocess.check_call(cmd, shell=False)
print(sp)

该脚本的输出(当returncode为零时):

# python3 exec_system_commands.py
command in list format: ['ping', '-c2', 'google.com']
PING google.com (172.217.26.238) 56(84) bytes of data.
64 bytes from bom05s09-in-f14.1e100.net (172.217.26.238): icmp_seq=1 ttl=115 time=579 ms
64 bytes from bom05s09-in-f14.1e100.net (172.217.26.238): icmp_seq=2 ttl=115 time=90.1 ms
--- google.com ping statistics --
2 packets transmitted, 2 received, 0% packet loss, time 68ms
rtt min/avg/max/mdev = 90.125/334.576/579.028/244.452 ms
0

该脚本的输出(当" returncode"非零时):

# python3 exec_system_commands.py
command in list format: ['ping', '-c2', 'google.co12m']
ping: google.co12m: Name or service not known
Traceback (most recent call last):
  File "exec_system_commands.py", line 11, in <module>
    sp = subprocess.check_call(cmd, shell=False)
  File "/usr/lib64/python3.6/subprocess.py", line 311, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['ping', '-c2', 'google.co12m']' returned non-zero exit status 2.

如我们所见,对于非零返回码,我们会得到subprocess.CalledProcessError

因此,我们应该使用try和,除了以下代码中使用的subprocess.check_now之外:

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ping -c2 google.c12om'.split()
# Check the list value of cmd
print('command in list format:',cmd)
try:
    sp = subprocess.check_call(cmd, shell=False)
except subprocess.CalledProcessError:
    print('Host is not reachable')

该脚本的输出(当返回码非零时):

# python3 exec_system_commands.py
command in list format: ['ping', '-c2', 'google.c12om']
ping: google.c12om: Name or service not known
Host is not reachable

所以现在这一次我们没有得到CalledProcessError,而是在控制台上打印了正确的stderr输出以及out print语句。

使用subprocess.run()函数

  • 在Python 3.5中添加了subprocess.run()函数

  • 等待命令完成,然后返回一个subprocess.CompletedProcess实例。

  • 完整的函数签名与Popen构造函数的签名基本相同-该函数的大多数参数都传递给该接口。 (timeoutinputcheckcapture_output不是。)

  • 如果capture_output为true,则将捕获stdoutstderr

  • 使用时,内部Popen对象将自动由stdout = PIPEstderr = PIPE创建。

  • stdoutstderr参数可能不能与capture_output同时提供。

  • 如果我们希望捕获两个流并将其合并为一个,请使用" stdout = PIPE"和" stderr = STDOUT"而不是" capture_output"。

在此示例python代码中,我们将尝试使用subprocess.run()检查互联网连接性

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ping -c2 google.c12om'.split()
# Check the list value of cmd
print('command in list format:',cmd)
sp = subprocess.run(cmd, shell=False, check=True, capture_output=True, text=True)
print("stdout: ", sp.stdout)
print("stderr: ", sp.stderr)

为了成功输出:

# python3 /tmp/script.py
command in list format: ['ping', '-c2', 'google.com']
stdout:  PING google.com (172.217.160.142) 56(84) bytes of data.
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=1 ttl=115 time=199 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=2 ttl=115 time=80.8 ms
--- google.com ping statistics --
2 packets transmitted, 2 received, 0% packet loss, time 1ms
rtt min/avg/max/mdev = 80.756/139.980/199.204/59.224 ms
stderr:

对于失败的输出,即当命令返回非零退出代码时:

# python3 /tmp/script.py
command in list format: ['ping', '-c2', 'google.c12om']
Traceback (most recent call last):
  File "/tmp/script.py", line 11, in <module>
    sp = subprocess.run(cmd, shell=False, check=True, capture_output=True, text=True)
  File "/usr/local/lib/python3.7/subprocess.py", line 512, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['ping', '-c2', 'google.c12om']' returned non-zero exit status 2.

说明:

capture_output仅受Python 3.7支持,如果与早期版本一起使用,则会出现TypeError:__init __()得到一个意外的关键字参数'capture_output'的情况。对于较旧的发行版,我们可以继续使用stdout = PIPEstderr = PIPE

如果我们不想在控制台上打印任何" ERROR",则可以使用" check = false",在这种情况下,输出将是:

# python3 exec_system_commands.py
command in list format: ['ping', '-c2', 'google.c12om']
ping: google.c12om: Name or service not known
stdout:  b''
stderr:  None

使用python subprocess.check_output()函数

  • subprocess.check_output()与subprocess.run(check = True)类似

  • 默认情况下,此函数将以编码字节的形式返回数据,因此我们可以使用text = True或者universal_newlines = True来获取字符串值作为输出

  • 该函数将运行带有参数的命令并返回其输出。

  • 如果返回码非零,则抛出CalledProcessError。 CalledProcessError对象将在returncode属性中具有返回码,在output属性中具有任何输出。

#!/usr/bin/env python3
import subprocess
# Define command as string and then split() into list format
cmd = 'ping -c2 google.c2om'.split()
# Check the list value of cmd
print('command in list format:',cmd)
try:
    output = subprocess.check_output(cmd, shell=False, stderr=subprocess.STDOUT, universal_newlines=True)
except subprocess.CalledProcessError as e:
    print('output before error: ',e.output)
    print('Return Code: ',e.returncode)

来自脚本的非零退出代码输出:

# python3 exec_system_commands.py
command in list format: ['ping', '-c2', 'google.c2om']
output before error:  ping: google.c2om: Name or service not known
Return Code:  2

我应该使用哪个子流程模块功能?

  • 函数run(),call(),check_call()和check_output()是Popen类的包装器。

  • 直接使用Popen可以更好地控制命令的运行方式以及其输入和输出流的处理方式。

等待命令完成

  • 使用subprocess.call或者subprocess.run运行args描述的命令。等待命令完成,然后返回" returncode"属性。

  • subprocess.Popenwait()结合使用以等待命令完成

其中我们使用subprocess.call来检查互联网连接,然后打印一些内容

#!/usr/bin/env python3
import subprocess
cmd='ping -c5 google.com'.split()
sp = subprocess.call(cmd, shell=False)
print('Something')

该脚本的输出:

# python3 exec_system_commands.py
PING google.com (172.217.160.142) 56(84) bytes of data.
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=1 ttl=115 time=102 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=2 ttl=115 time=325 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=3 ttl=115 time=85.4 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=4 ttl=115 time=249 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=5 ttl=115 time=81.0 ms
--- google.com ping statistics --
5 packets transmitted, 5 received, 0% packet loss, time 94ms
rtt min/avg/max/mdev = 81.022/168.509/324.751/99.872 ms
Something

因此我们知道subprocess.call会阻塞代码的执行,直到执行cmd为止。现在我们使用Popen(没有wait())执行相同的执行。

#!/usr/bin/env python3
import subprocess
cmd='ping -c5 google.com'.split()
sp = subprocess.Popen(cmd, shell=False)
print('Something')

如果我们观察到,在ping仍在进行时立即打印了某些内容,因此call()和run()是non-blocking函数。

# python3 exec_system_commands.py
Something
[root@client scripts]# PING google.com (172.217.160.142) 56(84) bytes of data.
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=1 ttl=115 time=107 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=2 ttl=115 time=136 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=4 ttl=115 time=122 ms
64 bytes from maa03s29-in-f14.1e100.net (172.217.160.142): icmp_seq=5 ttl=115 time=121 ms
--- google.com ping statistics --
5 packets transmitted, 4 received, 20% packet loss, time 396ms
rtt min/avg/max/mdev = 106.956/121.745/136.476/10.442 ms

说明:

subprocess.run()等效于:run(...)。returncode(输入和检查参数为不支持)。因此,我们可以根据需要选择使用run()或者call()函数。

捕获命令的输出

  • call()开始的进程的标准输入和输出通道绑定到父级的输入和输出。

  • 这意味着调用程序无法捕获命令的输出。使用check_output()捕获输出以供以后处理。

  • 为了防止将通过check_output()运行的命令产生的错误消息写入控制台,请将stderr参数设置为常量STDOUT

错误处理

  • 函数call()返回程序的退出代码,该代码是具有程序定义含义的整数,通常用于了解程序是成功还是失败。

  • check_call()函数的作用类似于call(),不同之处在于检查出口代码,如果它指示发生了错误,则会引发CalledProcessError异常。

  • check_output()函数返回程序打印的任何文本,如果程序以非零退出代码退出,则会引发异常。引发的异常具有一个称为output的属性,其中包含程序的文本输出。因此,即使程序退出并显示错误代码,我们仍然可以根据需要获取输出。