生命不息奮斗不止!
subprocess的目的就是啟動一個新的進程並且與之通信。
subprocess模塊中只定義了一個類: 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,子進程的環境變量將從父進程中繼承。
subprocess.PIPE
在創建Popen對象時,subprocess.PIPE可以初始化stdin, stdout或stderr參數。表示與子進程通信的標准流。
subprocess.STDOUT
創建Popen對象時,用於初始化stderr參數,表示將錯誤通過標准輸出流輸出。
Popen的方法:
Popen.poll()
用於檢查子進程是否已經結束。設置並返回returncode屬性。
Popen.wait()
等待子進程結束。設置並返回returncode屬性。
Popen.communicate(input=None)
與子進程進行交互。向stdin發送數據,或從stdout和stderr中讀取數據。可選參數input指定發送到子進程的參數。Communicate()返回一個元組:(stdoutdata, stderrdata)。注意:如果希望通過進程的stdin向其發送數據,在創建Popen對象的時候,參數stdin必須被設置為PIPE。同樣,如果希望從stdout和stderr獲取數據,必須將stdout和stderr設置為PIPE。
Popen.send_signal(signal)
向子進程發送信號。
Popen.terminate()
停止(stop)子進程。在windows平台下,該方法將調用Windows API TerminateProcess()來結束子進程。
Popen.kill()
殺死子進程。
Popen.stdin,Popen.stdout ,Popen.stderr ,官方文檔上這么說:
stdin, stdout and stderr specify the executed programs’ standard input, standard output and standard error file handles, respectively. Valid values are PIPE, an existing file descriptor (a positive integer), an existing file object, and None.
Popen.pid
獲取子進程的進程ID。
Popen.returncode
獲取進程的返回值。如果進程還沒有結束,返回None。
---------------------------------------------------------------
簡單的用法:
p=subprocess.Popen("dir", shell=True) p.wait()
shell參數根據你要執行的命令的情況來決定,上面是dir命令,就一定要shell=True了,p.wait()可以得到命令的返回值。
如果上面寫成a=p.wait(),a就是returncode。那么輸出a的話,有可能就是0【表示執行成功】。
---------------------------------------------------------------------------
進程通訊
如果想得到進程的輸出,管道是個很方便的方法,這樣:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (stdoutput,erroutput) = p.<span>commu</span>nicate()
p.communicate會一直等到進程退出,並將標准輸出和標准錯誤輸出返回,這樣就可以得到子進程的輸出了。
再看一個communicate的例子。
上面的例子通過communicate給stdin發送數據,然后使用一個tuple接收命令的執行結果。
------------------------------------------------------------------------
上面,標准輸出和標准錯誤輸出是分開的,也可以合並起來,只需要將stderr參數設置為subprocess.STDOUT就可以了,這樣子:
p=subprocess.Popen("dir", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (stdoutput,erroutput) = p.<span>commu</span>nicate()
如果你想一行行處理子進程的輸出,也沒有問題:
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
死鎖
但是如果你使用了管道,而又不去處理管道的輸出,那么小心點,如果子進程輸出數據過多,死鎖就會發生了,比如下面的用法:
p=subprocess.Popen("longprint", shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p.wait()
longprint是一個假想的有大量輸出的進程,那么在我的xp, Python2.5的環境下,當輸出達到4096時,死鎖就發生了。當然,如果我們用p.stdout.readline或者p.communicate去清理輸出,那么無論輸出多少,死鎖都是不會發生的。或者我們不使用管道,比如不做重定向,或者重定向到文件,也都是可以避免死鎖的。
----------------------------------
subprocess還可以連接起來多個命令來執行。
在shell中我們知道,想要連接多個命令可以使用管道。
在subprocess中,可以使用上一個命令執行的輸出結果作為下一次執行的輸入。例子如下:
例子中,p2使用了第一次執行命令的結果p1的stdout作為輸入數據,然后執行tail命令。
- -------------------
下面是一個更大的例子。用來ping一系列的ip地址,並輸出是否這些地址的主機是alive的。代碼參考了python unix linux 系統管理指南。
#!/usr/bin/env python from threading import Thread import subprocess from Queue import Queue num_threads=3 ips=['127.0.0.1','116.56.148.187'] q=Queue() def pingme(i,queue): while True: ip=queue.get() print 'Thread %s pinging %s' %(i,ip) ret=subprocess.call('ping -c 1 %s' % ip,shell=True,stdout=open('/dev/null','w'),stderr=subprocess.STDOUT) if ret==0: print '%s is alive!' %ip elif ret==1: print '%s is down...'%ip queue.task_done() #start num_threads threads for i in range(num_threads): t=Thread(target=pingme,args=(i,q)) t.setDaemon(True) t.start() for ip in ips: q.put(ip) print 'main thread waiting...' q.join();print 'Done'
在上面代碼中使用subprocess的主要好處是,使用多個線程來執行ping命令會節省大量時間。
假設說我們用一個線程來處理,那么每個 ping都要等待前一個結束之后再ping其他地址。那么如果有100個地址,一共需要的時間=100*平均時間。
如果使用多個線程,那么最長執行時間的線程就是整個程序運行的總時間。【時間比單個線程節省多了】
這里要注意一下Queue模塊的學習。
pingme函數的執行是這樣的:
啟動的線程會去執行pingme函數。
pingme函數會檢測隊列中是否有元素。如果有的話,則取出並執行ping命令。
這個隊列是多個線程共享的。所以這里我們不使用列表。【假設在這里我們使用列表,那么需要我們自己來進行同步控制。Queue本身已經通過信號量做了同步控制,節省了我們自己做同步控制的工作=。=】
代碼中q的join函數是阻塞當前線程。下面是e文注釋
Queue.join()
Blocks until all items in the queue have been gotten and processed(task_done()).
---------------------------------------------
學習Processing模塊的時候,遇到了進程的join函數。進程的join函數意思說,等待進程運行結束。