Python之执行命令(subprocess)

subprocess用来生成子进程,并可以通过管道连接他们的输入/输出/错误,以及获得他们的返回值。
subprocess用来替换多个旧模块和函数:os.system、os.spawn*、os.popen*、popen2.*、commands.*。在python中,通过标准库中的subprocess包来fork一个子进程,并且运行一个外部的程序。subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信

旧模块的使用

os.system()

执行操作系统的命令,将结果输出到屏幕,只返回命令执行状态0成功,非0失败

import os
>>> a = os.system("df -Th")
Filesystem   Type  Size Used Avail Use% Mounted on
/dev/sda3   ext4  1.8T 436G 1.3T 26% /
tmpfs     tmpfs  16G   0  16G  0% /dev/shm
/dev/sda1   ext4  190M 118M  63M 66% /boot
>>> a
0     
# 0 表示执行成功

# 执行错误的命令
>>> res = os.system("list")
sh: list: command not found
>>> res
32512    # 返回非 0 表示执行错误

os.popen()

执行操作系统的命令,会将结果保存在内存当中,可以用read()方法读取出来

import os
>>> res = os.popen("ls -l")
# 将结果保存到内存中
>>> print res
<open file 'ls -l', mode 'r' at 0x7f02d249c390>
# 用read()读取内容
>>> print res.read()
total 267508
-rw-r--r-- 1 root root  260968 Jan 27 2016 AliIM.exe
-rw-------. 1 root root   1047 May 23 2016 anaconda-ks.cfg
-rw-r--r-- 1 root root  9130958 Nov 18 2015 apache-tomcat-8.0.28.tar.gz
-rw-r--r-- 1 root root     0 Oct 31 2016 badblocks.log
drwxr-xr-x 5 root root   4096 Jul 27 2016 certs-build
drwxr-xr-x 2 root root   4096 Jul 5 16:54 Desktop
-rw-r--r-- 1 root root   2462 Apr 20 11:50 Face_24px.ico

subprocess的使用

subprocess模块中的常用函数

  1. subprocess.run()
    执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, universal_newlines=False)

# 应用举例:
>>> import subprocess
# python 解析则传入命令的每个参数的列表
>>> subprocess.run(["df","-h"])
Filesystem      Size Used Avail Use% Mounted on
/dev/mapper/VolGroup-LogVol00
           289G  70G 204G 26% /
tmpfs         64G   0  64G  0% /dev/shm
/dev/sda1       283M  27M 241M 11% /boot
CompletedProcess(args=['df', '-h'], returncode=0)
# 需要交给Linux shell自己解析,则:传入命令字符串,shell=True
>>> subprocess.run("df -h|grep /dev/sda1",shell=True)
/dev/sda1       283M  27M 241M 11% /boot
CompletedProcess(args='df -h|grep /dev/sda1', returncode=0)

subprocess.CompletedProcess类介绍

需要说明的是,subprocess.run()函数是Python3.5中新增的一个高级函数,其返回值是一个subprocess.CompletedPorcess类的实例,因此,subprocess.completedPorcess类也是Python 3.5中才存在的。它表示的是一个已结束进程的状态信息,它所包含的属性如下:

1. args: 用于加载该进程的参数,这可能是一个列表或一个字符串
2. returncode: 子进程的退出状态码。通常情况下,退出状态码为0则表示进程成功运行了;一个负值-N表示这个子进程被信号N终止了
3. stdout: 从子进程捕获的stdout。这通常是一个字节序列,如果run()函数被调用时指定universal_newlines=True,则该属性值是一个字符串。如果run()函数被调用时指定stderr=subprocess.STDOUT,那么stdout和stderr将会被整合到这一个属性中,且stderr将会为None
4. stderr: 从子进程捕获的stderr。它的值与stdout一样,是一个字节序列或一个字符串。如果stderr没有被捕获的话,它的值就为None
5. check_returncode(): 如果returncode是一个非0值,则该方法会抛出一个CalledProcessError异常

  1. subprocess.call()
    执行指定的命令,返回命令执行结果和状态,其功能类似于os.system(cmd)
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

# 应用举例:
>>> res = subprocess.call(["ls","-l"])
总用量 28
-rw-r--r-- 1 root root   0 6月 16 10:28 1
drwxr-xr-x 2 root root 4096 6月 22 17:48 _1748
-rw-------. 1 root root 1264 4月 28 20:51 anaconda-ks.cfg
drwxr-xr-x 2 root root 4096 5月 25 14:45 monitor
-rw-r--r-- 1 root root 13160 5月  9 13:36 npm-debug.log
# 命令执行状态
>>> res
0
  1. subprocess.check_call()
    执行指定的命令,如果执行成功则返回结果和状态码,否则抛出异常。其功能等价于subprocess.run(..., check=True)
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

# 应用举例:
>>> subprocess.check_call(["ls","-l"])
总用量 28
-rw-r--r-- 1 root root   0 6月 16 10:28 1
drwxr-xr-x 2 root root 4096 6月 22 17:48 _1748
-rw-------. 1 root root 1264 4月 28 20:51 anaconda-ks.cfg
drwxr-xr-x 2 root root 4096 5月 25 14:45 monitor
-rw-r--r-- 1 root root 13160 5月  9 13:36 npm-debug.log
0
>>> subprocess.check_call(["lm","-l"])
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/usr/lib64/python2.7/subprocess.py", line 537, in check_call
  retcode = call(*popenargs, **kwargs)
 File "/usr/lib64/python2.7/subprocess.py", line 524, in call
  return Popen(*popenargs, **kwargs).wait()
 File "/usr/lib64/python2.7/subprocess.py", line 711, in __init__
  errread, errwrite)
 File "/usr/lib64/python2.7/subprocess.py", line 1327, in _execute_child
  raise child_exception
OSError: [Errno 2] No such file or directory
  1. subprocess.check_output()
    执行指定的命令,如果执行状态码为0则返回命令执行结果,而不是打印,否则抛出异常。
subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)

# 应用举例:
>>> res = subprocess.check_output("pwd")
>>> res
b'/root\n' # 结果以字节形式返回
  1. subprocess.getoutput(cmd)
    接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()commands.getoutput(cmd)
subprocess.getstatusoutput(cmd)

# 应用举例:
>>> subprocess.getoutput('pwd')
'/root'
  1. subprocess.getstatusoutput(cmd)
    执行cmd命令,返回一个元组(命令执行状态, 命令执行结果输出),其功能类似于commands.getstatusoutput()
subprocess.getoutput(cmd)

# 应用举例:
#执行正确
>>> subprocess.getstatusoutput('pwd')
(0, '/root')
#执行错误
>>> subprocess.getstatusoutput('pd')
(127, '/bin/sh: pd: command not found')

常用函数中参数说明

  1. args
    要执行的shell命令,默认应该是一个字符串序列,序列的第一个元素通常都必须是一个可执行文件的路径,如['df', '-Th']('df', '-Th'),当然也可以使用executeable参数来指定可执行文件的路径;也可以是一个字符串,如'df -Th',但是此时需要把shell参数的值置为True。
  2. shell
    如果shell为True,那么指定的命令将通过shell执行。如果需要访问某些shell的特性,如管道、文件名通配符、环境变量扩展功能,这将是非常有用的。当然,python本身也提供了许多类似shell的特性的实现,如glob、fnmatch、os.walk()、os.path.expandvars()、os.expanduser()和shutil等。
  3. check
    如果check参数的值是True,且执行命令的进程以非0状态码退出,则会抛出一个CalledProcessError的异常,且该异常对象会包含 参数、退出状态码以及stdout和stderr(如果它们有被捕获的话)。
  4. universal_newlines
    该参数影响的是输入与输出的数据格式,比如它的值默认为False,此时stdout和stderr的输出是字节序列;当该参数的值设置为True时,stdout和stderr的输出是字符串
  5. stdin,stdout,stderr
    分别表示程序的标准输入、标准输出、标准错误。有效的值可以是PIPE,存在的文件描述符,存在的文件对象或None,如果为None需从父进程继承过来,stdout可以是PIPE,表示对子进程创建一个管道,stderr可以是STDOUT,表示标准错误数据应该从应用程序中捕获并作为标准输出流stdout的文件句柄。
  6. input
    • run()函数
      默认不会捕获命令执行结果的正常输出和错误输出,如果我们向获取这些内容需要传递subprocess.PIPE,然后可以通过返回的CompletedProcess类实例的stdout和stderr属性或捕获相应的内容
    • call()和check_call()函数
      返回的是命令执行的状态码,而不是CompletedProcess类实例,所以对于它们而言,stdout和stderr不适合赋值为subprocess.PIPE
    • check_output()函数
      默认就会返回命令执行结果,所以不用设置stdout的值,如果希望在结果中捕获错误信息,可以执行stderr=subprocess.STDOUT

subprocess.run()、subprocess.call()、subprocess.check_call()和subprocess.check_output()都是通过对subprocess.Popen的封装来实现的高级函数,因此如果我们需要更复杂功能时,可以通过subprocess.Popen来完成。

subprocess.Popen的构造函数

该类用于在一个新的进程中执行一个子程序。上面介绍的这些函数都是基于subprocess.Popen类实现的,通过使用这些被封装后的高级函数可以很方面的完成一些常见的需求。由于subprocess模块底层的进程创建和管理是由Popen类来处理的,因此,当无法通过上面哪些高级函数来实现一些不太常见的功能时就可以通过subprocess.Popen类提供的灵活的api来完成。

# 构造函数
class subprocess.Popen(
    args, 
    bufsize=-1, 
    executable=None, 
    stdin=None, 
    stdout=None, 
    stderr=None,     
    preexec_fn=None, 
    close_fds=True, 
    shell=False, 
    cwd=None, 
    env=None, 
    universal_newlines=False,
    startup_info=None, 
    creationflags=0, 
    restore_signals=True, 
    start_new_session=False, 
    pass_fds=()
    )

参数说明

  1. args
    要执行的shell命令,可以是字符串,也可以是命令各个参数组成的序列。当该参数的值是一个字符串时,该命令的解释过程是与平台相关的,因此通常建议将args参数作为一个序列传递。
  2. bufsize
    指定缓存策略,0表示不缓冲,1表示行缓冲,其他大于1的数字表示缓冲区大小,负数表示使用系统默认缓冲策略。
  3. stdin, stdout, stderr
    分别表示程序标准输入、输出、错误句柄。
  4. preexec_fn
    用于指定一个将在子进程运行之前被调用的可执行对象,只在Unix平台下有效。
  5. close_fds
    如果该参数的值为True,则除了0,1和2之外的所有文件描述符都将会在子进程执行之前被关闭。
  6. shell
    该参数用于标识是否使用shell作为要执行的程序,如果shell值为True,则建议将args参数作为一个字符串传递而不要作为一个序列传递。
  7. cwd
    如果该参数值不是None,则该函数将会在执行这个子进程之前改变当前工作目录。
  8. env
    用于指定子进程的环境变量,如果env=None,那么子进程的环境变量将从父进程中继承。如果env!=None,它的值必须是一个映射对象。
  9. universal_newlines
    如果该参数值为True,则该文件对象的stdin,stdout和stderr将会作为文本流被打开,否则他们将会被作为二进制流被打开。
  10. startupinfo和creationflags
    这两个参数只在Windows下有效,它们将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如主窗口的外观,进程优先级等。

subprocess.Popen类的实例可调用的方法

  1. Popen.poll()
    用于检查子进程(命令)是否已经执行结束,没结束返回None,结束后返回状态码。
  2. Popen.wait(timeout=None)
    等待子进程结束,并返回状态码;如果在timeout指定的秒数之后进程还没有结束,将会抛出一个TimeoutExpired异常。
  3. Popen.communicate(input=None, timeout=None)
    该方法可用来与进程进行交互,比如发送数据到stdin,从stdout和stderr读取数据,直到到达文件末尾。
    • 该方法中的可选参数input应该是将被发送给子进程的数据,如果没有数据发送给子进程,该参数应该是None。input参数的数据类型必须是字节串,如果universal_newlines参数值为True,则input参数的数据类型必须是字符串。
    • 该方法返回一个元组(stdout_data, stderr_data),这些数据将会是字节串或字符串(如果universal_newlines的值为True)。
    • 如果在timeout指定的秒数后该进程还没有结束,将会抛出一个TimeoutExpired异常。捕获这个异常,然后重新尝试通信不会丢失任何输出的数据。但是超时之后子进程并没有被杀死,为了合理的清除相应的内容,一个好的应用应该手动杀死这个子进程来结束通信。
    • 需要注意的是,这里读取的数据是缓冲在内存中的,所以,如果数据大小非常大或者是无限的,就不应该使用这个方法。
  4. Popen.send_signal(signal)
    发送指定的信号给这个子进程。
  5. Popen.terminate()
    停止该子进程。
  6. Popen.kill()
    杀死该子进程。
  7. Popen.pid
    获取子进程的进程ID。
  8. Popen.returncode
    获取进程的返回码。如果进程未结束,将返回None。

举例

  1. stdout
>>> import subprocess
>>> res = subprocess.Popen("ls /tmp/yum.log", shell=True, stdout=subprocess.PIPE) # 使用管道
>>> res.stdout.read()  # 标准输出
b'/tmp/yum.log\n'
res.stdout.close()  # 关闭
  1. stderr
>>> res = subprocess.Popen("lm -l",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# 标准输出为空
>>> res.stdout.read()
b''
#标准错误中有错误信息
>>> res.stderr.read()
b'/bin/sh: lm: command not found\n'
  1. poll()
>>> res = subprocess.Popen("sleep 10;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
>>> print(res.poll())
None
>>> print(res.poll())
None
>>> print(res.poll())
0
  1. wait()
>>> obj = subprocess.Popen("sleep 10;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
>>> obj.wait()
# 中间会一直等待
0
  1. terminate()
>>> res = subprocess.Popen("sleep 20;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
>>> res.terminate() # 结束进程
>>> res.stdout.read()
b''
  1. pid
import subprocess
>>> res = subprocess.Popen("sleep 5;echo 'hello'",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
>>> res.pid # 获取这个linux shell 的 进程号
3668
  1. communicate进程交互
    多次交互一次输出:
>>> obj = subprocess.Popen(["python"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> obj.stdin.write('print(1) \n')
>>> obj.stdin.write('print(2) \n')
>>> obj.stdin.write('print(3) \n')
>>> out,err = obj.communicate()
>>> print(out)
1
2
3

>>> print(err)

一次交互并输出:

>>> obj = subprocess.Popen(["python"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> out,err = obj.communicate(input='print(1) \n')
>>> print(out)
1

>>> print(err)
  1. 管道
    实现类似df -Th | grep data命令的功能,实际上就是实现shell中管道的共功能
# p1的输出=p2的输入,利用的是子进程之间的交互功能
>>> p1 = subprocess.Popen(['df', '-Th'], stdout=subprocess.PIPE)
>>> p2 = subprocess.Popen(['grep', 'data'], stdin=p1.stdout, stdout=subprocess.PIPE)
>>> out,err = p2.communicate()
>>> print(out)
/dev/vdb1      ext4      493G  4.8G  463G   2% /data
/dev/vdd1      ext4     1008G  420G  537G  44% /data1
/dev/vde1      ext4      985G  503G  432G  54% /data2

>>> print(err)
None