學習使用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()