【转】Python模块subprocess

subprocess

早期的Python版本中,我们主要是通过os.system()、os.popen().read()等函数、commands模块来执行命令行指令的,从Python 2.4开始官方文档新引入了一个模块subprocess,subprocess替换os.system等,允许你去创建一个新的进程让其执行另外的程序,并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等。 

import commands
result = commands.getoutput('cmd')
result = commands.getstatus('cmd')
result = commands.getstatusoutput('cmd')

subprocess的目的就是启动一个新的进程并且与之通信。

1. subprocess模块中的常用函数

函数描述
subprocess.run() Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。
subprocess.call()

父进程等待子进程执行命令,返回子进程执行命令的状态码,如果出现错误,不进行报错,其功能类似于os.system(cmd)。

res = subprocess.call(['dir',shell=True]) 获取的执行结果,我们能获取到的是子进程执行命令执行结果的状态码,即res=0/1 执行成功或者不成功,并不代表说看不到执行结果,在Python的console界面中我们是能够看到命令结果的,只是获取不到。想获取执行的返回结果,请看check_output。】

subprocess.check_call() Python 2.5中新增的函数。父进程等待子进程执行命令,返回执行命令的状态码,如果出现错误,进行报错【如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性,可用try…except…来检查】
subprocess.check_output() Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。
subprocess.getoutput(cmd) 接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd)。
subprocess.getstatusoutput(cmd) 执行cmd命令,返回一个元组(命令执行状态, 命令执行结果输出),其功能类似于commands.getstatusoutput()。

说明:

  1. 在Python 3.5之后的版本中,官方文档中提倡通过subprocess.run()函数替代其他函数来使用subproccess模块的功能;
  2. 在Python 3.5之前的版本中,我们可以通过subprocess.call(),subprocess.getoutput()等上面列出的其他函数来使用subprocess模块的功能;
  3. subprocess.run()、subprocess.call()、subprocess.check_call()和subprocess.check_output()都是通过对subprocess.Popen的封装来实现的高级函数,因此如果我们需要更复杂功能时,可以通过subprocess.Popen来完成。
  4. subprocess.getoutput()和subprocess.getstatusoutput()函数是来自Python 2.x的commands模块的两个遗留函数。它们隐式的调用系统shell,并且不保证其他函数所具有的安全性和异常处理的一致性。另外,它们从Python 3.3.4开始才支持Windows平台。

1. call:

2. check_call

返回结果

复制代码
2. ################## subprocess.check_call ##########
check_call与call命令相同,区别是如果出错会报错
 驱动器 D 中的卷没有标签。
 卷的序列号是 C6A1-5AD3

 D:ProgramPython 的目录

2016/01/27  13:05    <DIR>          .
2016/01/27  13:05    <DIR>          ..
2016/01/27  10:44    <DIR>          .idea
2016/01/27  11:23               159 log_analyse.py
2016/01/27  13:05             1,329 subprocessDemo.py
               2 个文件          1,488 字节
               3 个目录 26,335,281,152 可用字节
'abc' 不是内部或外部命令,也不是可运行的程序或批处理文件。                    这里是系统执行命令返回的系统报错
Traceback (most recent call last):                              这里是Python解释器返回的报错
  File "D:/Program/Python/subprocessDemo.py", line 19, in <module>
    subprocess.check_call(['abc'],shell=True)
  File "C:Python27libsubprocess.py", line 540, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['abc']' returned non-zero exit status 1
复制代码

3. check_output

父进程等待子进程执行命令,返回子进程向标准输出发送输出运行结果,检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性和output属性,output属性为标准输出的输出结果,可用try…except…来检查。

复制代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import subprocess

print "3. ################## subprocess.check_output ##############"
res1 = subprocess.call(['dir'],shell=True)
res2 = subprocess.check_call(['dir'],shell=True)
res3 = subprocess.check_output(['dir'],shell=True)
print u"call结果:",res1
print u"check_call结果:",res2
print u"check_output结果:
",res3
复制代码

返回结果:

复制代码
3. ################## subprocess.output ##############
 驱动器 D 中的卷没有标签。
 卷的序列号是 C6A1-5AD3

 D:ProgramPython 的目录

2016/01/27  13:14    <DIR>          .
2016/01/27  13:14    <DIR>          ..
2016/01/27  10:44    <DIR>          .idea
2016/01/27  11:23               159 log_analyse.py
2016/01/27  13:14             1,324 subprocessDemo.py
               2 个文件          1,483 字节
               3 个目录 26,334,232,576 可用字节
 驱动器 D 中的卷没有标签。
 卷的序列号是 C6A1-5AD3

 D:ProgramPython 的目录

2016/01/27  13:14    <DIR>          .
2016/01/27  13:14    <DIR>          ..
2016/01/27  10:44    <DIR>          .idea
2016/01/27  11:23               159 log_analyse.py
2016/01/27  13:14             1,324 subprocessDemo.py
               2 个文件          1,483 字节
               3 个目录 26,334,232,576 可用字节
call结果: 0
check_call结果: 0
check_output结果:
 驱动器 D 中的卷没有标签。
 卷的序列号是 C6A1-5AD3

 D:ProgramPython 的目录

2016/01/27  13:14    <DIR>          .
2016/01/27  13:14    <DIR>          ..
2016/01/27  10:44    <DIR>          .idea
2016/01/27  11:23               159 log_analyse.py
2016/01/27  13:14             1,324 subprocessDemo.py
               2 个文件          1,483 字节
               3 个目录 26,334,232,576 可用字节
复制代码

可见,call/check_call  返回值均是命令的执行状态码,而check_output返回值是命令的执行结果。

如果在执行相关命令时,命令后带有参数,将程序名(即命令)和所带的参数一起放在一个列表中传递给相关犯法即可,例如:

>>> import subprocess
>>> retcode = subprocess.call(["ls", "-l"])
>>> print retcode
0

4. Popen

  实际上,subprocess模块中只定义了一个类: Popen。上面的几个函数都是基于Popen()的封装(wrapper)。从Python2.4开始使用Popen来创建进程,用于连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向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)

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

a) 不等待的子进程

#!/usr/bin/env python
import subprocess

child = subprocess.Popen(['ping','-c','4','www.baidu.com'])
print 'hello'

执行结果:

复制代码
[root@localhost script]# python sub.py 
hello
[root@localhost script]# PING www.a.shifen.com (61.135.169.125) 56(84) bytes of data.
64 bytes from 61.135.169.125: icmp_seq=1 ttl=55 time=2.04 ms
64 bytes from 61.135.169.125: icmp_seq=2 ttl=55 time=1.58 ms
64 bytes from 61.135.169.125: icmp_seq=3 ttl=55 time=2.22 ms
64 bytes from 61.135.169.125: icmp_seq=4 ttl=55 time=2.13 ms

--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3008ms
rtt min/avg/max/mdev = 1.580/1.995/2.220/0.251 ms
复制代码

可以看出,Python并没有等到child子进程执行的Popen操作完成就执行了print操作。

b) 添加子进程等待

#!/usr/bin/env python
import subprocess

child = subprocess.Popen(['ping','-c','4','www.baidu.com'])  #创建一个子进程,进程名为child,执行操作ping -c 4 www.baidu.com
child.wait()                             #子进程等待
print 'hello'

执行结果:

复制代码
[root@localhost script]# python sub.py 
PING www.a.shifen.com (61.135.169.125) 56(84) bytes of data.
64 bytes from 61.135.169.125: icmp_seq=1 ttl=55 time=1.82 ms
64 bytes from 61.135.169.125: icmp_seq=2 ttl=55 time=1.65 ms
64 bytes from 61.135.169.125: icmp_seq=3 ttl=55 time=1.99 ms
64 bytes from 61.135.169.125: icmp_seq=4 ttl=55 time=2.08 ms

--- www.a.shifen.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3009ms
rtt min/avg/max/mdev = 1.656/1.889/2.082/0.169 ms
hello
复制代码

看出Python执行print操作是在child子进程操作完成以后才进行的。

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

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

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

方法描述
Popen.poll() 用于检查子进程(命令)是否已经执行结束,没结束返回None,结束后返回状态码。
Popen.wait(timeout=None) 等待子进程结束,并返回状态码;如果在timeout指定的秒数之后进程还没有结束,将会抛出一个TimeoutExpired异常。
Popen.communicate(input=None, timeout=None) 该方法可用来与进程进行交互,比如发送数据到stdin,从stdout和stderr读取数据,直到到达文件末尾。
Popen.send_signal(signal) 发送指定的信号给这个子进程。
Popen.terminate() 停止该子进程。
Popen.kill() 杀死该子进程。

关于communicate()方法的说明:

  • 该方法中的可选参数 input 应该是将被发送给子进程的数据,或者如没有数据发送给子进程,该参数应该是None。input参数的数据类型必须是字节串,如果universal_newlines参数值为True,则input参数的数据类型必须是字符串。
  • 该方法返回一个元组(stdout_data, stderr_data),这些数据将会是字节穿或字符串(如果universal_newlines的值为True)。
  • 如果在timeout指定的秒数后该进程还没有结束,将会抛出一个TimeoutExpired异常。捕获这个异常,然后重新尝试通信不会丢失任何输出的数据。但是超时之后子进程并没有被杀死,为了合理的清除相应的内容,一个好的应用应该手动杀死这个子进程来结束通信。
  • 需要注意的是,这里读取的数据是缓冲在内存中的,所以,如果数据大小非常大或者是无限的,就不应该使用这个方法。

3. subprocess.Popen使用实例

实例1:

>>> import subprocess
>>>
>>> p = subprocess.Popen('df -Th', stdout=subprocess.PIPE, shell=True)
>>> print(p.stdout.read())
Filesystem     Type      Size  Used Avail Use% Mounted on
/dev/vda1      ext4       40G   12G   26G  31% /
devtmpfs       devtmpfs  3.9G     0  3.9G   0% /dev
tmpfs          tmpfs     3.9G     0  3.9G   0% /dev/shm
tmpfs          tmpfs     3.9G  386M  3.5G  10% /run
tmpfs          tmpfs     3.9G     0  3.9G   0% /sys/fs/cgroup
tmpfs          tmpfs     783M     0  783M   0% /run/user/0
tmpfs          tmpfs     783M     0  783M   0% /run/user/1000

实例2:

>>> obj = subprocess.Popen(["python"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
>>> obj.stdin.write('print(1) 
')
>>> obj.stdin.write('print(2) 
')
>>> obj.stdin.write('print(3) 
')
>>> out,err = obj.communicate()
>>> print(out)
1
2
3

>>> print(err)

实例3:

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

>>> print(err)

实例4:

实现类似df -Th | grep data命令的功能,实际上就是实现shell中管道的共功能。

>>> 
>>> 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

四、总结


那么我们到底该用哪个模块、哪个函数来执行命令与系统及系统进行交互呢?下面我们来做个总结:

  • 首先应该知道的是,Python2.4版本引入了subprocess模块用来替换os.system()、os.popen()、os.spawn*()等函数以及commands模块;也就是说如果你使用的是Python 2.4及以上的版本就应该使用subprocess模块了。
  • 如果你的应用使用的Python 2.4以上,但是是Python 3.5以下的版本,Python官方给出的建议是使用subprocess.call()函数。Python 2.5中新增了一个subprocess.check_call()函数,Python 2.7中新增了一个subprocess.check_output()函数,这两个函数也可以按照需求进行使用。
  • 如果你的应用使用的是Python 3.5及以上的版本(目前应该还很少),Python官方给出的建议是尽量使用subprocess.run()函数。
  • 当subprocess.call()、subprocess.check_call()、subprocess.check_output()和subprocess.run()这些高级函数无法满足需求时,我们可以使用subprocess.Popen类来实现我们需要的复杂功能。

 

原文地址:https://www.cnblogs.com/yoyo008/p/9487551.html