subprocess 模块简介

官网:https://docs.python.org/3.6/library/subprocess.html
subprocess模块用来生成子进程,并可以通过管道连接它们的输入/输出/错误,以及获得它们的返回值。
它用来代替多个旧模块和函数:
os.system
os.spawn*

subprocess模块中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

1、subprocess.run()函数:用于执行一个外部命令,但该方法不能返回执行的结果,只能返回执行的状态码: 成功(0) 或 错误(非0) 

    run()函数是在Python 3.5中添加的,如果在老版本中使用,需要下载并扩展

[root@localhost 20170722]# pip install subprocess.run
Collecting subprocess.run
  Downloading subprocess.run-0.0.8.tar.gz
Installing collected packages: subprocess.run
  Running setup.py install for subprocess.run ... done
Successfully installed subprocess.run-0.0.8

    使用方法 :subprocess.run(args*stdin=Noneinput=Nonestdout=Nonestderr=Noneshell=Falsecwd=Nonetimeout=Nonecheck=Falseencoding=Noneerrors=None)

    常用参数:

  • args

        args是所有调用所必需的,应该为一个字符串或一个程序参数序列。通常倾向提供参数序列,因为它允许这个模块来处理任何所需的转义和引用参数(例如,允许文件名中的空格)。如果传递单个字符串,shell必须为True(见下文),否则字符串必须简单地命名要执行的程序而不指定任何参数。

  • stdin、stdout和stderr

       stdin、stdout和stderr分别指定执行程序的标准输入,标准输出和标准错误文件句柄。有效值有PIPEDEVNULL,一个存在的文件描述器(正整数),一个存在的文件对象和None。PIPE表示应该为子进程创建新的管道。DEVNULL表示将使用特殊文件os.devnull。使用默认设置None,则不会发生重定向;子进程的文件句柄将从父进程继承。此外,stderr可以是STDOUT,表示来自子进程的标准错误数据应该捕获到与stdout相同的文件句柄中。

  • shell

        如果shell是True,则将通过shell执行指定的命令。如果你使用Python主要是由于它能提供大多数系统shell不能提供的增强的控制流,并且仍然希望方便地访问其他shell功能,如shell管道、文件名通配符、环境变量扩展和扩展〜到用户的主目录,这会很有用。

  • check

        检查returncode是否为0,不为0则抛出错误

    运行args描述的命令。等待命令完成,然后返回一个CompleteProcess实例;

    完整的函数形式很大程度上与Popen构造函数相同 —— 除timeout、input和check之外,该函数的所有参数都传递给Popen接口

>>> subprocess.run(["ls", "-l"])
#shell命令ls -l执行结果;此结果并不会被捕获
total 16
-rwxr-xr-x 1 root root 330 Jul 19 06:02 run.py
-rw-r--r-- 1 root root 437 Jul 18 23:20 run.pyc
-rwxr-xr-x 1 root root 319 Jul 18 23:27 sub.py
-rwxr-xr-x 1 root root 155 Jul 18 23:41 test.py
#subprocess.run()返回结果
CompletedProcess(args=['ls', '-l'], returncode=0)
>>> subprocess.run("exit 1", shell=True, check=True)
#check=True会检查返回的returncode是否为0,若不是0则会报错
#shell=True表示args的字符串以shell原生命令运行
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/subprocess.py", line 418, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command 'exit 1' returned non-zero exit status 1.
>>> subprocess.run("uname -r",shell=True)
3.10.0-514.el7.x86_64            #shell命令执行结果,标准输出
CompletedProcess(args='uname -r', returncode=0)            #subprocess.run()返回结果

#只显示标准输出
>>> subprocess.run("uname -r",shell=True).stdout
3.10.0-514.el7.x86_64

#不显示标准输出,将标准输出通过管道subprocess.PIPE输出
>>> a = subprocess.run("uname -r",shell=True,stdout=subprocess.PIPE)
>>> a
CompletedProcess(args='uname -r', returncode=0, stdout=b'3.10.0-514.el7.x86_64\n')
>>> a.stdout
b'3.10.0-514.el7.x86_64\n'

#只返回returncode,相当有linux中执行命令后的返回码(echo $?)
>>> subprocess.run("uname -r",shell=True,stdout=subprocess.PIPE).returncode
0            #0表示命令执行成功

2、subprocess.call() 函数

   功能和subprocess.run()近似,差别在于run函数返回的是一个CompleteProcess实例,call函数返回的只是returncode

#subprocess.call()
>>> subprocess.call("ls -l",shell=True)
total 16
-rwxr-xr-x 1 root root 330 Jul 19 06:02 run.py
-rw-r--r-- 1 root root 437 Jul 18 23:20 run.pyc
-rwxr-xr-x 1 root root 319 Jul 18 23:27 sub.py
-rwxr-xr-x 1 root root 155 Jul 18 23:41 test.py
0

#subprocess.run()
>>> subprocess.run("ls -l",shell=True)
total 16
-rwxr-xr-x 1 root root 330 Jul 19 06:02 run.py
-rw-r--r-- 1 root root 437 Jul 18 23:20 run.pyc
-rwxr-xr-x 1 root root 319 Jul 18 23:27 sub.py
-rwxr-xr-x 1 root root 155 Jul 18 23:41 test.py
CompletedProcess(args='ls -l', returncode=0)

3、subprocess.check_call() 方法 :错误处理

    我们说过call执行返回一个状态码,我们可以通过check_call()函数来检测命令的执行结果,如果不成功将返回 subprocess.CalledProcessError 异常

    类似subprocess.run()中check=True参数的功能

#subprocess.run()
>>> subprocess.run("la -l",shell=True,check=True)
/bin/sh: la: command not found
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/subprocess.py", line 418, in run
    output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command 'la -l' returned non-zero exit status 127.
#subprocess.check_call()
>>> subprocess.check_call("la -l",shell=True)
/bin/sh: la: command not found
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/subprocess.py", line 291, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command 'la -l' returned non-zero exit status 127.

4、subprocess.check_output():捕获命令执行结果

    subprocess.run和subprocess.call不会捕获shell命令执行的结果,要获取命令执行结果需要check_output()函数

>>> p = subprocess.check_output("ls -l",shell=True)
>>> p
b'total 16\n-rwxr-xr-x 1 root root 330 Jul 19 06:02 run.py\n-rw-r--r-- 1 root root 437 Jul 18 23:20 run.pyc\n-rwxr-xr-x 1 root root 319 Jul 18 23:27 sub.py\n-rwxr-xr-x 1 root root 155 Jul 18 23:41 test.py\n'

5、subprocess.getout(command):直接返回shell命令执行的结果

    command是字符串类型的shell原生命令;

#将shell命令ls -l /的执行结果赋值给p
>>> p = subprocess.getoutput("ls -l /")
>>> p
'total 24\n-rw-r--r--    1 root root    0 Jul 18 13:03 1\nlrwxrwxrwx.   1 root root    7 Jun 26 15:35 bin -> usr/bin\ndr-xr-xr-x.   4 root root 4096 Jun 26 15:45 boot\ndrwxr-xr-x   20 root root 3200 Jul 18 11:19 dev\ndrwxr-xr-x. 138 root root 8192 Jul 18 13:15 etc\ndrwxr-xr-x.   4 root root   31 Jun 27 05:36 home\nlrwxrwxrwx.   1 root root    7 Jun 26 15:35 lib -> usr/lib\nlrwxrwxrwx.   1 root root    9 Jun 26 15:35 lib64 -> usr/lib64\ndrwxr-xr-x.   2 root root    6 Nov  5  2016 media\ndrwxr-xr-x.   2 root root  105 Jul 18 17:25 mnt\ndrwxr-xr-x.   3 root root   16 Jul 18 13:14 opt\ndr-xr-xr-x  108 root root    0 Jul 18 11:19 proc\ndr-xr-x---.  12 root root 4096 Jul 19 06:02 root\ndrwxr-xr-x   34 root root  900 Jul 18 13:17 run\nlrwxrwxrwx.   1 root root    8 Jun 26 15:35 sbin -> usr/sbin\ndrwxr-xr-x.   2 root root    6 Nov  5  2016 srv\ndr-xr-xr-x   13 root root    0 Jul 18 11:19 sys\ndrwxrwxrwt.  12 root root  281 Jul 19 07:11 tmp\ndrwxr-xr-x.  13 root root  155 Jun 26 15:35 usr\ndrwxr-xr-x.  21 root root 4096 Jul 18 13:13 var'
#支持shell管道
>>> p = subprocess.getoutput("ls -l /|wc -l")
>>> p
'21'

6、subprocess.getstatusoutput():返回returncode和shell命令执行结果组成的一个元组

>>> subprocess.getstatusoutput('ls /bin/ls')
(0, '/bin/ls')
>>> subprocess.getstatusoutput("ls -l /")
(0, 'total 24\n-rw-r--r--    1 root root    0 Jul 18 13:03 1\nlrwxrwxrwx.   1 root root    7 Jun 26 15:35 bin -> usr/bin\ndr-xr-xr-x.   4 root root 4096 Jun 26 15:45 boot\ndrwxr-xr-x   20 root root 3200 Jul 18 11:19 dev\ndrwxr-xr-x. 138 root root 8192 Jul 18 13:15 etc\ndrwxr-xr-x.   4 root root   31 Jun 27 05:36 home\nlrwxrwxrwx.   1 root root    7 Jun 26 15:35 lib -> usr/lib\nlrwxrwxrwx.   1 root root    9 Jun 26 15:35 lib64 -> usr/lib64\ndrwxr-xr-x.   2 root root    6 Nov  5  2016 media\ndrwxr-xr-x.   2 root root  105 Jul 18 17:25 mnt\ndrwxr-xr-x.   3 root root   16 Jul 18 13:14 opt\ndr-xr-xr-x  107 root root    0 Jul 18 11:19 proc\ndr-xr-x---.  12 root root 4096 Jul 19 06:02 root\ndrwxr-xr-x   34 root root  900 Jul 18 13:17 run\nlrwxrwxrwx.   1 root root    8 Jun 26 15:35 sbin -> usr/sbin\ndrwxr-xr-x.   2 root root    6 Nov  5  2016 srv\ndr-xr-xr-x   13 root root    0 Jul 18 11:19 sys\ndrwxrwxrwt.  12 root root  281 Jul 19 07:11 tmp\ndrwxr-xr-x.  13 root root  155 Jun 26 15:35 usr\ndrwxr-xr-x.  21 root root 4096 Jul 18 13:13 var')

Popen直接处理管道

 subprocess.Popen()方法:函数call(), check_call() 和 check_output() 都是基于Popen()的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。

    

构造函数如下:

subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)

    1. wait()方法:与上面的封装不同,Popen对象创建后,主程序不会自动等待子进程完成。我们可以调用对象的wait()方法,父进程才会等待 (也就是阻塞block)

  • 不调用wait()方法
#!/usr/bin/env python3

import subprocess
child = subprocess.Popen(["ping","-c","5","www.baidu.com"])
print("parent process")
执行结果:
[root@localhost 20170722]# ./pipe.py 
parent process
[root@localhost 20170722]# PING www.a.shifen.com (180.97.33.108) 56(84) bytes of data.
64 bytes from 180.97.33.108 (180.97.33.108): icmp_seq=1 ttl=55 time=8.57 ms
64 bytes from 180.97.33.108 (180.97.33.108): icmp_seq=2 ttl=55 time=7.58 ms
64 bytes from 180.97.33.108 (180.97.33.108): icmp_seq=3 ttl=55 time=7.93 ms
64 bytes from 180.97.33.108 (180.97.33.108): icmp_seq=4 ttl=55 time=10.7 ms
64 bytes from 180.97.33.108 (180.97.33.108): icmp_seq=5 ttl=55 time=11.5 ms

--- www.a.shifen.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4017ms
rtt min/avg/max/mdev = 7.585/9.283/11.565/1.584 ms
#从运行结果中看到,父进程在开启子进程之后并没有等待child的完成,而是直接运行print。
  • 调用wait()方法
#!/usr/bin/env python3

import subprocess
child = subprocess.Popen(["ping","-c","5","www.baidu.com"])
child.wait()
print("parent process")
执行结果:
[root@localhost 20170722]# ./pipe_wait.py 
PING www.a.shifen.com (180.97.33.107) 56(84) bytes of data.
64 bytes from 180.97.33.107 (180.97.33.107): icmp_seq=1 ttl=55 time=7.15 ms
64 bytes from 180.97.33.107 (180.97.33.107): icmp_seq=2 ttl=55 time=7.73 ms
64 bytes from 180.97.33.107 (180.97.33.107): icmp_seq=3 ttl=55 time=8.76 ms
64 bytes from 180.97.33.107 (180.97.33.107): icmp_seq=4 ttl=55 time=9.95 ms
64 bytes from 180.97.33.107 (180.97.33.107): icmp_seq=5 ttl=55 time=10.8 ms

--- www.a.shifen.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4017ms
rtt min/avg/max/mdev = 7.157/8.894/10.859/1.373 ms
parent process
#从运行结果中看到,子进程调用wait()函数后;父进程会在子进程child完成后在运行print

注:在调用wait()方法是容易产生死锁现象;使用 subprocess 模块的 Popen 调用外部程序,如果 stdout 或 stderr 参数是 pipe,并且程序输出超过操作系统的 pipe size时,如果使用 Popen.wait() 方式等待程序结束获取返回值,会导致死锁,程序卡在 wait() 调用上。

linux中ulimt -a可以查看到pipe size大小

[root@localhost 20170722]# ulimit -a 
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7208
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 7208
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

    2. communicate():除了wait()方法阻塞父进程外,我们也可以使用communicate()方法;而且使用communicate()方法会避免死锁出现,因为这个方法会把输出放在内存,而不是管道里,所以这时候上限就和内存大小有关了,一般不会有问题。而且如果要获得程序返回值,可以在调用 Popen.communicate() 之后取 Popen.returncode 的值。

    communicate()会返回一个元组(stdout,stderr)

#!/usr/bin/env python3

import subprocess
child = subprocess.Popen(["ls","-l","/tmp"],stdout=subprocess.PIPE)
out = child.communicate()        #stdout输出结果
code = child.returncode        #returncode值
print(out,"\n",code)
执行结果:
[root@localhost 20170722]# ./pipe_communicate.py 
(b'total 116\ndrwxr-xr-x  2 root root    85 Jul 18 17:32 20170720\ndrwxr-xr-x  2 root root   103 Jul 19 05:58 20170721\ndrwxr-xr-x  2 root root   120 Jul 19 12:35 20170722\ndrwxr-xr-x  2 root root    18 Jul 18 13:08 hsperfdata_root\ndrwxr-xr-x. 2 root root    30 Jun 27 05:27 shutil_file\n-rw-------  1 root root 99005 Jul 18 11:27 yum_save_tx.2017-07-18.11-27.uzI4eR.yumtx\n-rw-------  1 root root 15118 Jul 18 12:53 yum_save_tx.2017-07-18.12-53.vANJxP.yumtx\n', None) 
 0

   参考:Popen.wait()和Popen.communicate()区别

    此外,你还可以在父进程中对子进程进行其它操作,比如我们上面例子中的child对象:

  • child.poll()                  # 检查子进程状态
  • child.kill()                   # 终止子进程
  • child.send_signal()    # 向子进程发送信号
  • child.terminate()        # 终止子进程
  • 子进程的PID存储在child.pid

    3. 子进程文本流控制

    (沿用child子进程) 子进程的标准输入,标准输出和标准错误也可以通过如下属性表示:

    child.stdin

    child.stdout

    child.stderr

利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe):

#!/usr/bin/env python3

import subprocess
child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE)
out = child2.communicate()
print(out)
执行结果:
[root@localhost 20170722]# ./popen_pipe.py 
(b'      8      65     354\n', None)

    subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。

subprocess模块执行python脚本指定环境 subprocess python_shell命令