学习使用python已经有四个月了,subprocess这个执行linux中shell命令的函数已经用过无数次了,踩到的坑也有几个,写出来分享一下,欢迎大家拍砖头。
1.shell命令中若有管道,一定要多次调用Poen,p1的输出当作p2的输入。
例如:shell命令
hdfs dfs -cat test.log.lzo | lzop -d | head -n 2
此命令可以查看hdfs上面一个lzo文件中的前两行,开始没有看subprocess的手册,直接代码就写成:
shell_comm="hdfs dfs -cat test.log.lzo | lzop -d | head -n 2"
outPipe=subprocess.Popen(shell_comm,shell=True,stdout=subprocess.PIPE)
结果输出那是一堆乱码,纠结了半天,一直以为是编码不统一的问题,找了N久,郁闷良久,最后老大跟我说,subprocess不是这样用的,管道必须使用多个popen,代码改成:
comm1="hdfs dfs -cat test.log.lzo"
comm2="lzop -d"
comm3="head -n 2"
p1=subprocess.Popen(comm1,shell=True,stdout=subprocess.PIPE)
p2=subprocess.Popen(comm2,shell=True,stdoin=p1.stdout,stdout=subprocess.PIPE)
p3=subprocess.Popen(comm3,shell=True,stdoin=p2.stdout,stdout=subprocess.PIPE)
最后p3的输出就是你想要的结果了
2.python2.7多个管道连接输出会出现Broken pipe提示。
像上面代码中多个管道连续输出,最后p3却只取前2行,comm1|2在一直不停的执行,comm3却终止了,此时就会出现Broken pipe提示,这是python的一个bug,具体原因和解决办法可见http://bugs.python.org/issue1652 https://code.google.com/p/python-subprocess32/
3.subprocess一定要收集子进程状态
这就牵扯到我写代码过程中跳的一个坑,看网上写的使用subprocess的例子,都是直接执行命令,然后读取PIPE内容,我也照做,根本没有想到收集什么进程状态这回事。有一天,老大把我叫过去,看着我说:“我发现一个问题,为什么每次你写的这个程序运行起来,服务器内存使用量一下子就上升了2、3十G?”
经过各种traceback和lsof查看进程状态,发现罪魁祸首居然是subprocess,最重要的是我调用了subprocess.Popen之后没有收集子进程状态。我的整个程序运行了5、6分钟,打开了hdfs上的上百个文件,而且都是取开头两行,开启了N个子进程,都没有收集,那么这些子进程的数据全部都算在了程序主进程中,一直占用服务器内存,并且越堆积越多。最后加上状态收集语句之后问题解决。
4.subprocess.wait()与subprocess.communicate()使用问题
subprocess就是开启一个子进程,自己去执行命令,这个子进程的状态肯定得收集,这时候就需要调用wait或者communicate了,手册上面也注明了这两个方法的特点:在数据超过PIPE的缓存时,wait会阻塞进程;communicate会把所有的数据都读取到内存中。
wait:
Warning
This will deadlock if the child process generates enough output to a stdout or stderr pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.
communicate:
Note
The data read is buffered in memory, so do not use this method if the data size is large or unlimited
那么现在就有一个问题了,当我shell命令执行的结果很大时,我是该用wait还是communicate?用wait直接就阻塞了,肯定不行,用communicate也不行,如果很大的文件,数据都保存在内存,主机直接就卡死了。
解决办法:数据一行一行读取,读取完之后wait,这样既保证了不会阻塞(PIPE中数据有进有出,最后空了才wait),又保证了不会占用大量主机内存(在内存中的数据只有一行line)。
p1=subprocess.Popen(comm1,shell=True,stdout=subprocess.PIPE)
for line in p1.stdout:
pass
p1.wait()