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()。 |
说明:
- 在Python 3.5之后的版本中,官方文档中提倡通过subprocess.run()函数替代其他函数来使用subproccess模块的功能;
- 在Python 3.5之前的版本中,我们可以通过subprocess.call(),subprocess.getoutput()等上面列出的其他函数来使用subprocess模块的功能;
- subprocess.run()、subprocess.call()、subprocess.check_call()和subprocess.check_output()都是通过对subprocess.Popen的封装来实现的高级函数,因此如果我们需要更复杂功能时,可以通过subprocess.Popen来完成。
- 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类来实现我们需要的复杂功能。