subprocess
*****本文參考了Vamei大神的http://www.cnblogs.com/vamei/archive/2012/09/23/2698014.html
運用subprocess包可以在運行python的進程下進一步開啟一個子進程,創建子進程要注意
1. 父進程是否暫停
2.創建出的子進程返回了什么
3.執行出錯,即返回的code不是0的時候應該如何處理
subprocess包提供了三個開啟子進程的方法,subprocess.call() , subprocess.check_call() , subprocess.check_output(),給三者傳遞命令字符串作為參數。可以用(['ping','www.baidu.com','-c','3'])這種列表的形式,同時也可以是("ping www.baidu.com -c 3") 這種形式。在開啟子進程的時候,可以加上shell=True的參數來讓python開啟一個shell,通過shell來解釋獲得的命令。一般在windows下運行的程序最好都把shell=True加上,這樣才能順利地執行dos命令,但是linux下似乎不加也沒啥關系。因為linux下未指明用shell執行的話會調用/bin/sh來執行,問題不大,但是dos下系統不會默認用cmd.exe來執行命令,所以要加上shell=True。
subprocess.call ; subprocess.check_call ; subprocess.check_output 這三者的區別在於,返回的值分別是,子進程的執行返回碼;若返回碼是0則返回0,否則出錯的話raise起CalledProcessError,可以用except處理之;若返回碼是0則返回子進程向stdout輸出的結果,否則也raise起CalledProcessError。另外,這三個方法都是讓父進程掛起等待的,在子進程結束之前,父進程不會繼續往下運行。
另外從本質上講,上述三個方法都是對subprocess.Popen方法的一個包裝,Popen開啟的子進程是不會讓父進程等待其完成的,除非調用了wait()方法:
child = subprocess.Popen("...",shell=True) print "Hello" """ 很可能hello在子進程的輸出之前就被打印出來了,因為父進程不等child子進程運行完 """ child = subprocess.Popen("...",shell=True) child.wait() print "Hello" """ 這就不一樣,父進程一定會等子進程運行完,給出完整的結果之后再繼續往下執行。相當於wait函數掛起了父進程。 """
此外,上面代碼里的child這個對象還有其他的一些方法:
child.poll() 返回子進程運行狀態,主要是兩種結果,None代表尚未運行完,而一個返回碼則代表已經運行完成並且是成功或失敗了
child.kill() 強行終止子進程
child.send_signal(...) 向子進程發送一個信號(具體信號是以什么方式表示不清楚,還待研究)
child.terminate() 終止子進程
child.pid 子進程的pid
child.returncode 子進程的返回碼
child.stdin/stdout/stderr 子進程的標准輸入流,標准輸出和標准錯誤輸出,都是類文件對象
■ 文本流控制
每個子進程對象都有stdin/stdout/stderr三個對象,而在Popen開啟子進程的時候,可以設置這三個對象。比如
child1 = subprocess.Popen("cmd1",shell=True, stdout=subprocess.PIPE) ''' child1的stdout被設置成管道,可以把它理解成一個第三方托管機構, 因為不設置的話child1的stdout的內容就直接被打印到父進程的stdout里了, 設置成管道之后內容被導入了PIPEという名の第三方托管機構里 ''' child2 = subprocess.Popen("cmd2",shell=True,stdin=child1.stdout,stdout=subprocess.PIPE) ''' 把child2的stdin設置成了child1的stdout,也就是之前那個第三方機構,這么一來就實現了兩個子進程之間的數據通信了。
而把child2的stdout也設置成第三方,是因為不想讓child2的輸出就直接這么輸出到父進程的stdout里,而要對它做一些處理 ''' stdout,tmp = child2.communicate() ''' 因為child2的輸出不用再轉給個child3去處理,就用communicate方法把第三方機構那里的數據取出來放進一個變量里。
注意,這里的stdout已經是個str對象了,communicate出來的都是字符串了
communicate方法自帶wait功能,會讓父進程掛起等待所有子進程結束
communicate會返回一個元組,但是像在這個例子中沒有設置stderr=PIPE,所以元組中的第二項原本屬於stderr的值的地方的tmp的值是None,如果設置了其為PIPE,由於沒有錯誤信息tmp是""。這點是有區別的。 ''' print "We have result:\n%s"%(stdout) #代表了把stdout做一些處理后再輸出
■ Popen方法
Popen就是開啟一個新的子進程,常用的幾個參數正如上面所提的cmd,shell,stdin,stdout,stderr來指定開啟的子進程的一些屬性。
除此之外還有以下的參數:
close_fds 默認為False,設置為True的情況下會在子進程執行之前關閉所有除了0,1,2之外的所有文件都關閉(雖然不知道有什么意義)
cwd 默認None,可以為子進程設置工作目錄
盜了張圖(http://www.cnblogs.com/zhoug2020/p/5079407.html)
實際上,我一般都是這么干的:
import subprocess p = subprocess.Popen("CMD",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout,stderr = p.communicate() if stderr != "": print "ERROR:"+stderr else: print "RESULT:"+stdout
*這篇文章(http://www.tuicool.com/articles/bUNJ7v)提供了一個解決大量並發開啟子進程時出bug的方案,同時也給了一個如何為一個子進程設置超時的方法,值得看一下:
def timeout_command(command, timeout): start = datetime.datetime.now() process = subprocess.Popen(command, bufsize=10000, stdout=subprocess.PIPE, close_fds=True) ''' 這個循環就是為子進程設置了超時功能,感覺還挺巧妙的。。 ''' while process.poll() is None: time.sleep(0.1) now = datetime.datetime.now() if (now - start).seconds> timeout: try: process.terminate() except Exception,e: return None return None out = process.communicate()[0] if process.stdin: process.stdin.close() if process.stdout: process.stdout.close() if process.stderr: process.stderr.close() try: process.kill() except OSError: pass return out
■ 關於實時獲取子進程輸出的方法
之前用過的所有subprocess.Popen方法吧,打開的子進程都比較短小,其命令基本上都可以在一秒內完成。所以在communicate的時候都沒有顯示出什么不妥的地方。但是碰到一些比較大,運行時間比較長的命令時,communicate就顯得有些不太好了,因為到命令運行完成或者緩沖區滿為止,子進程對象是不會向程序返回輸出內容的。此時就需要變通一點不要使用communicate了。
一個解決的辦法是這樣的:
import subprocess import sys p = subprocess.Popen('cmd',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) while p.poll() is None: #當子進程沒有完成之前 char = p.stdout.read(1) sys.stdout.write(char) stdout,stderr = p.communicate() sys.stdout.write(stdout)
觀察這段程序,可以看到,當子進程沒有結束之前,循環將不斷地從子進程的stdout中讀取一個字符的數據然后寫到父進程的stdout中。如果子進程較長時間沒有明文進入stdout的話也可以在循環中加上一個time.sleep來控制循環頻率。下面再加上communicate是為了保證信息輸出的完整性。因為當子進程結束之后,有可能stdout還沒有讀取光,如果不加communicate的話那么還剩余在stdout中的信息就丟失了。如果子進程在stderr中有輸出那么也可以放在communicate后面判斷。另外也可以在建立p對象的時候把stderr參宿設置為subprocess.STDOUT來把子進程的stderr輸出重定向到stdout中。
*測試的時候,把一個每sleep1秒就向stdout寫入一些信息的腳本當做子進程。但是發現以上方法並不奏效。想了下之后,記起來python在寫文件的時候是會有緩沖區這個設定的。也就是說,子進程代碼中的write被調用后stdout並不馬上把信息輸出到stdout中。解決辦法就是用file對象(這里是sys.stdout)調用flush()方法來清空緩存並寫入文件。