调用外部命令/可执行文件

Python中可以执行shell命令的相关模块和函数有:

os.system
os.spawn*
os.popen*          --废弃
popen2.*           --废弃
commands.*      --废弃,3.x中被移除

随着Python版本的更新,过多的模块引起代码的复杂与冗余,因此Python新引入了一个模块subprocess,将以上几个模块中的功能集中到它当中,以后我们只需import这一个即可。

1、subprocess模块

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

  • 运行python的时候,我们都是在创建并运行一个进程。像Linux进程那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序。在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序。
  • subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

1.1 call

父进程等待子进程执行命令,返回子进程执行命令的状态码(returncode,相当于Linux exit code),如果出现错误,不进行报错。

  • 这里说的返回执行命令的状态码的意思是:如果我们通过一个变量 res = subprocess.call(['dir',shell=True]) 获取的执行结果,我们能获取到的是子进程执行命令执行结果的状态码,即res=0/1 执行成功或者不成功,并不代表说看不到执行结果,在Python的console界面中我们是能够看到命令结果的,只是获取不到。想获取执行的返回结果,请看check_output。
  • 不进行报错解释:如果我们执行的命令在执行时,操作系统不识别,系统会返回一个错误,如:abc命令不存在,这个结果会在console界面中显示出来,但是我们的Python解释器不会提示任何信息,如果想让Python解释器也进行报错,请看check_call
    示例
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import subprocess
print "1. ################## subprocess.call ###############"
print u"call方法调用系统命令进行执行,如果出错不报错"
subprocess.call(['dir'],shell=True)

:shell默认为False,在Linux下,shell=False时, Popen调用os.execvp()执行args指定的程序;shell=True时,如果args是字符串,Popen直接调用系统的Shell来执行args指定的程序,如果args是一个序列,则args的第一项是定义程序命令字符串,其它项是调用系统Shell时的附加参数。
  在Windows下,不论shell的值如何,Popen调用CreateProcess()执行args指定的外部程序。如果args是一个序列,则先用list2cmdline()转化为字符串,但需要注意的是,并不是MS Windows下所有的程序都可以用list2cmdline来转化为命令行字符串。在windows下,调用脚本时要写上shell=True。

1.2 check_call

父进程等待子进程执行命令,返回执行命令的状态码,如果出现错误,进行报错。如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性,可用try…except…来检查。
示例

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import subprocess

print "2. ################## subprocess.check_call ##########"
print u"check_call与call命令相同,区别是如果出错会报错"
subprocess.check_call(['dir'],shell=True)
subprocess.check_call(['abc'],shell=True)
print u"call方法与check_call方法都知识执行并打印命令到输出终端,但是 
 获取不到,如果想获取到结果使用check_output"

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结果:\n",res3

可见,call/check_call 返回值均是命令的执行状态码,而check_output返回值是命令的执行结果。
如果在执行相关命令时,命令后带有参数,将程序名(即命令)和所带的参数一起放在一个列表中传递给相关方法即可,例如:

import subprocess

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

1.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)
  • 参数args可以是字符串或者序列类型(如:list,元组),用于指定进程的可执行文件及其参数。如果是序列类型,第一个元素通常是可执行文件的路径。我们也可以显式的使用executeable参数来指定可执行文件的路径。
  • 参数stdin, stdout, stderr分别表示程序的标准输入、输出、错误句柄。他们可以是PIPE,文件描述符或文件对象,也可以设置为None,表示从父进程继承。
  • 如果参数shell设为true,程序将通过shell来执行。
  • 参数env是字典类型,用于指定子进程的环境变量。如果env = None,子进程的环境变量将从父进程中继承。

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

#!/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'

此外也可以在父进程中对子进程进行其它操作。

child.poll() # 用于检查子进程是否已经结束。设置并返回returncode属性。
child.wait() # 等待子进程结束。设置并返回returncode属性。

Popen.communicate(input=None) 
# 与子进程进行交互。向stdin发送数 据,或从stdout和stderr中读取数  
据。可选参数input指定发送到子进程的参数。Communicate()返回一个元 
组:(stdoutdata, stderrdata)。注意:如果希望通过进程的stdin向其发送  
数据,在创建Popen对象的时候,参数 stdin必须被设置为PIPE。同样, 
如果希望从stdout和stderr获取数据,必 须将stdout和stderr设置为PIPE。
    
child.kill() # 终止子进程
child.send_signal() # 向子进程发送信号
child.terminate() # 终止子进程
child.pid # 获取子进程的进程ID。
child.returncode # 获取进程的返回值。如果进程还没有结束,返回None。

子进程文本流控制
子进程的标准输入、标准输出和标准错误如下属性分别表示:
child.stdin | child.stdout | child.stderr
我们还可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe),如下2个例子:
例1

#!/usr/bin/env python
import subprocess

child = subprocess.Popen(['ls','-l'],stdout=subprocess.PIPE)  #将标准输出定向输出到subprocess.PIPE
print child.stdout.read()   #使用 child.communicate()  也可

例2

#!/usr/bin/env python
import subprocess

child1 = subprocess.Popen(['cat','/etc/passwd'],stdout=subprocess.PIPE)
child2 = subprocess.Popen(['grep','root'],stdin=child1.stdout,stdout=subprocess.PIPE)

print child2.communicate()

subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。
注意:communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成

1.5 进程通信

如果想得到进程的输出,管道是个很方便的方法。

  • 简单情况

例1

p=subprocess.Popen("dir", shell=True)  
p.wait()  

subprocess.call(["ls", "-l"])

subprocess.call("exit 1", shell=True)

shell参数根据你要执行的命令的情况来决定,上面是dir命令,就一定要shell=True了,p.wait()可以得到命令的返回值。
如果上面写成a=p.wait(),a就是returncode。那么输出a的话,有可能就是0【表示执行成功】。

  • 分开输出

例2

p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)  
(stdoutput,erroutput) = p.communicate()  

p.communicate会一直等到进程退出,并将标准输出和标准错误输出返回,这样就可以得到子进程的输出了。

  • 合并输出

例3

p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  
(stdoutput,erroutput) = p.communicate()  

标准输出和标准错误输出是分开的,也可以合并起来,只需要将stderr参数设置为subprocess.STDOUT就可以了。

  • 逐行输出

例4

p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  
while True:  
    buff = p.stdout.readline()  
    if buff == '' and p.poll() != None:  
        break  
  • 死锁

如果使用了管道,而又不去处理管道的输出,如果子进程输出数据过多,死锁就会发生了。
例5

p=subprocess.Popen("longprint", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)  
p.wait() 

longprint是一个假想的有大量输出的进程,假设在xp, Python2.5的环境下,当输出达到4096时,死锁就发生了。当然,如果我们用p.stdout.readline或者p.communicate去清理输出,那么无论输出多少,死锁都是不会发生的。或者我们不使用管道,比如不做重定向,或者重定向到文件,也都是可以避免死锁的。

  • 多命令执行

在shell中我们知道,想要连接多个命令可以使用管道。
在subprocess中,可以使用上一个命令执行的输出结果作为下一次执行的输入。

你可能感兴趣的:(调用外部命令/可执行文件)