Python 目前已经废弃了 os.system、os.spawn*、os.popen*、popen2.*、commands.* 来执行其他语言的命令,取而代之的是 subprocess 模块。
运行 Python 的时候,我们都是在创建并运行一个进程。像 Linux 进程那样,一个进程可以 fork 一个子进程,并让这个子进程 exec 另外一个程序。在 Python 中,我们通过标准库中的 subprocess 包来 fork 一个子进程,并运行一个外部的程序。
subprocess 允许你能创建很多子进程,创建的时候能指定子进程和子进程的 input/output/error 管道,执行后能获取输出结果和执行状态。
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
运行 args 描述的命令,等待命令完成后返回状态码,如果超过 timeout,子进程将会被 kill 掉,并再次等待。子进程被终止后会抛出 TimeoutExpired 异常。该方法必须等待命令执行完毕,会阻塞主进程。
注意:针对该函数,不要使用 stdout = PIPE 或 stderr = PIPE。因为不是从当前进程中读取管道(pipe),如果子进程没有生成足够的输出来填充 os 的管道缓冲区,可能会阻塞子进程。其中 shell 参数为 False 时,命令需要通过列表的方式传入,当 shell 为 True 时,可直接传入命令 。
In [54]: subprocess.call("df -h", shell=True)
Filesystem Size Used Avail Use% Mounted on
udev 7.8G 0 7.8G 0% /dev
tmpfs 1.6G 18M 1.6G 2% /run
/dev/sda3 909G 8.7G 855G 2% /
tmpfs 7.8G 98M 7.7G 2% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
/dev/sda2 512M 3.5M 509M 1% /boot/efi
tmpfs 1.6G 64K 1.6G 1% /run/user/1000
Out[54]: 0
In [56]: subprocess.call(['df','-h'], shell=False)
Filesystem Size Used Avail Use% Mounted on
udev 7.8G 0 7.8G 0% /dev
tmpfs 1.6G 18M 1.6G 2% /run
/dev/sda3 909G 8.7G 855G 2% /
tmpfs 7.8G 98M 7.7G 2% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
/dev/sda2 512M 3.5M 509M 1% /boot/efi
tmpfs 1.6G 64K 1.6G 1% /run/user/1000
Out[56]: 0
subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)
用法与 subprocess.call() 类似,如果执行状态码是 0 ,则返回0,否则抛异常。同样会阻塞主进程。
subprocess.check_output(args, *, stdin=None, stderr=None, shell=False, universal_newlines=False, timeout=None)
用法与上面两个方法类似,区别是,如果当返回值为0时,直接返回输出结果,如果返回值不为0,直接抛出异常。同样会阻塞主进程。
在一些复杂场景中,我们需要将一个进程的执行输出作为另一个进程的输入。在另一些场景中,我们需要先进入到某个输入环境,然后再执行一系列的指令等。
subprocess.Popen(args, bufsize=0, stdin=None, stdout=None, stderr=None,
shell=False, cwd=None,env=None,universal_newlines=False)
Popen 对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的 wait() 方法,父进程才会等待 (也就是阻塞block)。
该方法有以下参数:
args:必填参数shell命令,可以是字符串,或者序列类型,如 list, tuple
bufsize:缓冲区大小,可不用关心
stdin,stdout,stderr:分别表示程序的标准输入,标准输出及标准错误
shell:与上面方法中用法相同,如果shell为True,指定命令将通过shell执行。出于安全考虑,如果命令字符串参数需要通过外部的输入来构成的时候,强烈建议设置shell = False,不然容易造成shell注入之类的。
cwd:用于设置子进程的当前目录
env:用于指定子进程的环境变量。如果env=None,则默认从父进程继承环境变量
universal_newlines:不同系统的的换行符不同,当该参数设定为true时,则表示使用\n作为换行符(Unix行结束符:'\n', Windows行结束符:'\r\n')
示例:
# 在/root下创建一个suprocesstest的目录
import subprocess
a = subprocess.Popen('mkdir subprocesstest',shell=True,cwd='/root')
# 将一个子进程的输出,作为另一个子进程的输入
child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["grep","0:0"],stdin=child1.stdout, stdout=subprocess.PIPE)
out = child2.communicate()
Popen 类实例有以下几个方法:
child = subprocess.Popen('sleep 60',shell=True,stdout=subprocess.PIPE)
注意:
Popen() # 创建子进程并执行,该子进程执行并不会阻塞父进程,该子进程和当前父进程是异步执行的。
child.poll() # 检查子进程状态,是否中断,设置并返回returncode
child.kill() # 终止子进程
child.send_signal() # 向子进程发送信号
child.terminate() # 终止子进程
child.wait() # 等待子进程终止,设置并返回returncode。如果进程在timeout(单位秒)之后依然
#没终止,则抛出TimeoutExpired异常,可以捕获该异常并再次尝试等待。wait 函数会阻塞当前父进程,等待子进程运行结束,子进程结束之后返回子进程的退出值,0表示正常执行完成。
警告
当使用stdout = PIPE and / or stderr = PIPE时,如果子进程生成足够的输出到管道,这会阻止操作系统管道缓冲区接收更多数据,进而造成死锁。
为了避免该事件,使用 communicate()
child.communicate(input=None, timeout=None)
input:可选参数,参数值为发送给子进程的数据,如果不需要发送数据,则为None。如果universal_newlines为 False,则input数据类型必须为字节,否则可为字符串。
communicate() 返回一个元组:(stdoutdata, stderrdata),会阻塞当前父进程,直到子进程执行完成,父进程才会往下继续执行。与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。
注意:
如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。
如果进程在timeout(单位秒)之后依然没终止,则抛出 TimeoutExpired 异常。子进程不会被 kill 掉,所以为了完成交互,恰当的清理友好执行的程序,应该 kill 子进程。
with subprocess.Popen(['dir'], stdout=subprocess.PIPE,stderr=subprocess.PIPE, shell=True, universal_newlines = True) as proc:
try:
outs, errs = proc.communicate(timeout=15) #超时时间为15秒
print(outs, errs)
except subprocess.TimeoutExpired:
proc.kill()
outs, errs = proc.communicate()
print(outs, errs)
注意:读取的数据缓存在内存,所以如果数据太大或者无限制,不要使用该函数。
subprocess.PIPE 可用于Popen函数stdin,stdout或者stderr参数的指特定值,表示必须打开一个指向标准流的管道。
subprocess.STDOUT 可用于Popen函数stdin,stdout或者stderr参数的指特定值,表示标准错误信息必须一起写入同样的句柄,比如标准输出。
参考:
http://blog.sina.com.cn/s/blog_13cc013b50102wv83.html
https://blog.csdn.net/songfreeman/article/details/50735045
https://www.cnblogs.com/sunailong/p/5162748.html
https://blog.csdn.net/seetheworld518/article/details/48805261
https://hacpai.com/article/1462524113048
https://blog.csdn.net/ronnyjiang/article/details/53333538