你可以做到這一點與subprocess
,但它不是微不足道的。如果您查看文檔中的Frequently Used Arguments,您會發現可以通過PIPE
作爲stderr
參數,該參數創建一個新管道,將管道的一端傳遞給子進程,並使另一端可用作stderr
屬性。*
因此,您將需要服務該管道,寫入屏幕和文件。一般情況下,爲此獲取詳細信息非常棘手。**在您的情況下,只有一個管道,並且您計劃同步進行維護,所以沒有那麼糟糕。
import subprocess
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
for line in proc.stderr:
sys.stdout.write(line)
log_file.write(line)
proc.wait()
(注意有使用for line in proc.stderr:
一些問題-basically,如果你正在讀證明不是要行緩衝以任何理由,你可以坐在那裏等待換行符,即使實際上有一半需要處理一行數據,如果需要,你可以一次讀取數據塊,例如read(128)
,甚至可以使用read(1)
來獲得數據更加平滑的數據,如果你需要一到達每一個字節就可以得到數據,並且可以承擔read(1)
的費用,您需要將管道置於非阻塞模式並異步讀取。)
但是,如果您使用的是Unix,使用tee
命令可能會更簡單。
對於快速的&骯髒的解決方案,您可以使用shell來穿過它。這樣的事情:
subprocess.call('path_to_tool -option1 option2 2|tee log_file 1>2', shell=True,
stdout=file_out)
但我不想調試殼管道;讓我們做它在Python,如圖in the docs:
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=file_out, stderr=subprocess.PIPE)
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stderr)
tool.stderr.close()
tee.communicate()
最後,有十幾個或更多的圍繞子流程和/或PyPI- sh
,shell
,shell_command
,shellout
外殼更高層次的包裝,搜索「shell」,「subprocess」,「process」,「command line」等,找到一個你喜歡的,這使得問題變得微不足道。
如果您需要收集stderr和stdout,該怎麼辦?
最簡單的方法就是將其中一個重定向到另一個,就像Sven Marnach在評論中提出的那樣。只要改變Popen
參數是這樣的:
tool = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
然後到處使用tool.stderr
,使用tool.stdout
代替-e.g,對於最後一個例子:
tee = subprocess.Popen(['tee', 'log_file'], stdin=tool.stdout)
tool.stdout.close()
tee.communicate()
但是這有一定的權衡。最明顯的是,將兩個流混合在一起意味着您不能將stdout記錄到file_out和stderr以log_file,或將stdout複製到stdout和stderr到您的stderr。但是這也意味着排序可能是非確定性的 - 如果在將任何內容寫入stdout之前,子進程總是寫兩行到stderr,那麼一旦混合了這些流,您可能會在這兩行之間得到一堆stdout。這意味着他們必須共享stdout的緩衝模式,所以如果你依賴的是linux/glibc保證stderr被行緩衝(除非子進程明確地改變它),這可能不再是真實的。
如果您需要分開處理這兩個過程,則會變得更加困難。早些時候,我說過,只要你只有一根管道並且可以同步維修,就可以輕鬆地維修管道。如果你有兩個管道,那顯然不再是真的。想象一下,你正在等待tool.stdout.read()
,新數據來自tool.stderr
。如果數據太多,可能會導致管道溢出並阻塞子進程。但即使這種情況沒有發生,您顯然將無法讀取並記錄stderr數據,直到從stdout中輸入內容爲止。
如果您使用pipe-through-tee
解決方案,那就避免了最初的問題......但只能通過創建一個同樣糟糕的新項目。你有兩個tee
實例,當你打電話給communicate
時,另一個坐在一旁等待。
因此,無論哪種方式,都需要某種異步機制。你可以做到這一點是與線程,select
反應堆,如gevent
,等等。
這裏有一個快速和骯髒的例子:
proc = subprocess.Popen(['path_to_tool', '-option1', 'option2'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def tee_pipe(pipe, f1, f2):
for line in pipe:
f1.write(line)
f2.write(line)
t1 = threading.Thread(target=tee_pipe, args=(proc.stdout, file_out, sys.stdout))
t2 = threading.Thread(target=tee_pipe, args=(proc.stderr, log_file, sys.stderr))
t3 = threading.Thread(proc.wait)
t1.start(); t2.start(); t3.start()
t1.join(); t2.join(); t3.join()
然而,也有一些邊緣情況下,這是行不通的。 (問題在於SIGCHLD和SIGPIPE/EPIPE/EOF到達的順序,我不認爲這會影響到我們,因爲我們沒有發送任何輸入信息......但是不要相信我通過和/或測試)。從3.3+的subprocess.communicate
函數獲得所有的細節。但是您可能會發現使用PyPI和ActiveState上可以找到的一個異步子進程包裝器實現,或者甚至是像Twisted這樣的完整異步框架中的子進程內容,都會更加簡單。
*的文檔並不真正說明什麼管道是,彷彿他們希望你是一個老Unix下C手......但一些例子,特別是在Replacing Older Functions with the subprocess
Module部分,展示他們如何是使用,而且非常簡單。
**困難的部分是正確排序兩個或多個管道。如果你等待一個管道,另一個可能會溢出並阻塞,從而阻止你等待另一個管道完成。解決這個問題的唯一簡單方法是創建一個線程來服務每個管道。 (在大多數* nix平臺上,您可以使用select
或poll
電抗器,但是使該跨平臺非常困難。)The source模塊,特別是communicate
及其幫助程序顯示如何執行此操作。 (我鏈接到3.3,因爲在早期版本中,communicate
本身有一些重要的錯誤...)這就是爲什麼,只要有可能,如果您需要多個管道,則要使用communicate
。在你的情況下,你不能使用communicate
,但幸運的是你不需要多個管道。
請問你的代碼需要在Windows(或其他非POSIXy平臺)工作的?如果不是,則有一個更簡單的答案。 – abarnert
它不需要! –
相關:[Python子進程獲取兒童輸出到文件和終端?](http://stackoverflow.com/q/4984428/4279) – jfs