参考 http://zsl-oo7.blog.163.com/blog/static/353297032013101101820439/
一:基本概念
subprocess模块中只定义了一个类,Popen
用Popen的构造函数创建一个进程:
p=Popen(["nslookup","www.baidu.com","8.8.8.8"],shell=True)
# 这句话执行完,会打印 nslookup www.baidu.com 8.8.8.8 的结果在屏幕上
其中,第一个参数是一个list,list里面第一个字符串是命令,后面的字符串是命令的参数们
第二个参数shell=True,表示这个命令是要在shell里执行的,而不是运行一个可执行文件。
运行可执行文件一般这么写(这里就不用写shell=True了,shell的默认是False):
p=Popen(["/usr/sbin/bind9/named","-f","/etc/named/named.conf"])
==================================================================
二、子进程输出的文字打印到哪里:PIPE
p=Popen(....)创建了一个进程p,p被创建后立刻开始执行。
当p有输出时,输出被送往p的stdout,默认是None,None表示继承其父的,即运行当前python的终端
#coding:utf-8
fromimportPopen
import time
#进程被创建后自己去执行了,python继续往下走,开始打印Main process....
#当p有输出时,送往p的stdout,默认是None,即继承其父的,即运行当前python的终端
#所以p的结果也会在终端中显示,跟主进程的打印交替
p=Popen(["nslookup","www.baidu.com","8.8.8.8"],shell=True)
forin(10):
print"Main Process.....%d"%i
.sleep(0.1)
当然可以在创建p时指定其stdout,将其输出放到一个缓冲区里,而不直接放到屏幕上。
这个缓冲区就是subprocess.PIPE,这就好像在子进程和缓冲区之间插了一根管子
例子:
#coding:utf-8
fromimportPopen,PIPE
import time
#指定子进程的stdout为缓冲区,而不是终端屏幕(继承自父)
p=Popen(["nslookup","www.baidu.com","8.8.8.8"],shell=True,stdout=PIPE,stderr=PIPE)
forin(10):
print"Main Process.....%d"%i
.sleep(0.1)
#把缓冲区里东西打出来
print p.stderr.read()
print.stdout.read()
PIPE 会阻塞python主进程:如果子进程的stdout过多(大于65536字节)的话。
启动这种子进程的时候,最好把它的stdout指向一个文件:
stdout=open('log.log','w')
如果stdout/stderr对你来说没有用,想直接扔掉的话,可以重定向到 os.devnull
stdout=open(os.devnull,'w')
sederr=open(os.devnull,'w')
os.devnull 在linux上就是 /dev/null,在windows上就是 nul
另外,如果你把stdout/stderr等重定向到文件,而且你需要处理这个文件,那么你必须让操作系统强行把这个文件的修改同步了,不然可能会打不开文件或者你读的时候进程写的东西还没被同步到文件里等等情况:
# 假设要把 stdout 重定向到 log_file
# 你先往log_file里写点儿东西 log_file.write("ba la ba la")
# 一定要先调用下面两行确保你想写的东西已经写磁盘了,然后再开子进程去写log_file
log_file.flush()
os.fsync(log_file.fileno())
Popen(cmd, stdout=log_file, stderr=log_file).wait()
======================================================
三、容易让人误解的 Popen.returncode
Popen 有一个attribute 叫 returncode,官方文档里写的是,当p未执行完时,p.returncode是None。
我以为,当p执行完了,p.returncode就会自己变成非None,变成p退出时返回的值,但我错了。
看例子 :
(这个例子需要两个py源文件,分别代表主程序和子进程,主程序文件名:p.py,子进程文件名:child.py)
#coding:utf-8
#p.py
importas sbp
import time
p=sbp.Popen(["python","child.py",],shell=True)
forin(10):
print"p.returncode :",p.returncode
.sleep(2)#coding:utf-8
#child.py
import time
print"child process is started, It will be terminated in 10 seconds"
time.sleep(10)
print"child process exits with returncode -1"
exit(-1)p.returncode :None
child process is,Itin10 seconds
p.returncode :None
p.returncode :None
p.returncode :None
p.returncode :None
p.returncode :None
child process exits with-1
p.returncode :None
p.returncode :None
p.returncode :None
p.returncode :None
这是因为p产后,p就是操作系统的一个进程了,当他退出时,他会告诉操作系统“我退出了,返回码是xx”,而不是告诉python。
p不会主动的跟python交流,需要python主动去问p,python问到了之后,再自己维护p的attribute。
python 可以用p.poll()去问p。p.poll()查询进程p的状态,如果p已经结束了,则把p.returncode设为其返回码,函数的返回值就是这个返回码。如果没结束,则p.poll()返回None。
例子:
将上例的主程序改动一行,其他不变。
#coding:utf-8
#p.pyimport subprocess as sbp
import time
p=sbp.Popen(["python","child.py",],shell=True)
forin(10):
print"p.poll(): ",p.poll()
.sleep(2)p.poll():None
child process is,Itin10 seconds
p.poll():None
p.poll():None
p.poll():None
p.poll():None
p.poll():None
child process exits with-1
p.poll():-1
p.poll():-1
p.poll():-1
p.poll():-1
p.wait()跟p.poll()功能类似,都是查询p的状态并设置p.returncode。不同的是,p.wait()要等到p结束后才返回,而p.poll()查一下就返回,不管p结束了没有。
============================================
四、一个例子
一个常见的场景是,我们的python程序需要调用某个操作系统的命令,等待其执行结束,对其返回值进行处理。
例如,我们要从ipconfig命令中提取ip地址:
#coding:utf-8
from subprocess import Popen,PIPE
p=Popen(["ipconfig",],shell=True,stdout=PIPE)
p.wait() #这句一定要写,要不然可能ipconfig的结果还没出来呢,你就开始处理它的输出文本了!
out_text=p.stdout.read()
# do something with out_text.....
print out_text[:10]
由于上的用法太常见了,所以subprocess提供了简单方法,将以上过程封装成了3个函数,详见下一节。
============================================