2011-10-11 54 views
8

也許以前有人可以幫我解決這個問題。 (我已經看到了一些類似的問題在SO上,但沒有處理標準錯誤和標準錯誤,或處理與我的情況相當,因此這個新問題。)如何在不失真的情況下打印和顯示子處理stdout和stderr輸出?

我有一個python函數打開一個子進程,等待它完成,然後輸出返回代碼,以及標準輸出和標準錯誤管道的內容。在進程正在運行的同時,我還想在填充它們時顯示兩個管道的輸出。我的第一次嘗試導致了這樣的事情:

process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

stdout = str() 
stderr = str() 
returnCode = None 
while True: 
    # collect return code and pipe info 
    stdoutPiece = process.stdout.read() 
    stdout = stdout + stdoutPiece 
    stderrPiece = process.stderr.read() 
    stderr = stderr + stderrPiece 
    returnCode = process.poll() 

    # check for the end of pipes and return code 
    if stdoutPiece == '' and stderrPiece == '' and returnCode != None: 
     return returnCode, stdout, stderr 

    if stdoutPiece != '': print(stdoutPiece) 
    if stderrPiece != '': print(stderrPiece) 

雖然有一些問題。由於read()的讀數爲EOF,因此while循環的第一行將不會返回,直到子進程關閉管道。

我可以替換read()轉而使用read(int),但打印的輸出會失真,在讀取的字符結束時會被截斷。我可以用readline()作爲替代,但是當兩者同時出現很多時,打印的輸出會以交替的輸出線和錯誤線扭曲。可能有read-until-end-of-buffer()變種,我不知道?或者,也許它可以實施?

也許最好按answer to another post的建議實施sys.stdout包裝?然而,我只想在這個函數中使用包裝器。

社區的其他想法?

我很感激幫助! :)

編輯:解決方案確實應該是跨平臺的,但如果您有不是的想法,請分享它們以保持頭腦風暴。


對於我的蟒蛇頭子的刮管另外一個,看看我的另一問題上accounting for subprocess overhead in timing

+0

你可能想看看像pexpect。 –

+0

爲什麼不只是創建一個StringIO並將同一個實例傳遞給子進程的stdout和stderr? –

+0

@NathanErnst:因爲那不行。 'stdout'和'stderr'必須是真實的OS級文件描述符。 –

回答

10

使用fcntl.fcntl使管道非阻塞,並使用select.select等待數據在任一管道中變得可用。例如:

# Helper function to add the O_NONBLOCK flag to a file descriptor 
def make_async(fd): 
    fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) 

# Helper function to read some data from a file descriptor, ignoring EAGAIN errors 
def read_async(fd): 
    try: 
     return fd.read() 
    except IOError, e: 
     if e.errno != errno.EAGAIN: 
      raise e 
     else: 
      return '' 

process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 
make_async(process.stdout) 
make_async(process.stderr) 

stdout = str() 
stderr = str() 
returnCode = None 

while True: 
    # Wait for data to become available 
    select.select([process.stdout, process.stderr], [], []) 

    # Try reading some data from each 
    stdoutPiece = read_async(process.stdout) 
    stderrPiece = read_async(process.stderr) 

    if stdoutPiece: 
     print stdoutPiece, 
    if stderrPiece: 
     print stderrPiece, 

    stdout += stdoutPiece 
    stderr += stderrPiece 
    returnCode = process.poll() 

    if returnCode != None: 
     return (returnCode, stdout, stderr) 

注意fcntl只在類Unix平臺,包括Cygwin的使用。

如果你需要它在沒有Cygwin的情況下在Windows上工作,它是可行的,但它要困難得多。你必須:

+0

這也可以在Windows中完成?我看起來像'fcntl'只是unix,'select'也有一些規定。我應該在我的問題中提到解決方案需要跨平臺。我會補充一點。儘管這仍然很酷!謝謝! – perden

+0

我注意到fcntl僅適用於Unix平臺。如果他在Windows窗口中,或者希望這是不可知的,那麼這個解決方案將不起作用。 –

+0

@perden:見編輯 –

0

合併this answerthis,下面的代碼工作對我來說:

import subprocess, sys 
p = subprocess.Popen(args, stderr=sys.stdout.fileno(), stdout=subprocess.PIPE) 
for line in iter(p.stdout.readline, ""): 
print line, 
+0

如果要將stderr和stdout結合使用,更常用的方法是使用'stderr = subprocess.STDOUT'。 – dbn

0

當我測試了它,它似乎的ReadLine()阻塞。不過,我能夠使用線程分別訪問stdout和stderr。代碼示例如下:

import os 
import sys 
import subprocess 
import threading 

class printstd(threading.Thread): 
    def __init__(self, std, printstring): 
     threading.Thread.__init__(self) 
     self.std = std 
     self.printstring = printstring 
    def run(self): 
     while True: 
      line = self.std.readline() 
      if line != '': 
      print self.printstring, line.rstrip() 
      else: 
      break 

pythonfile = os.path.join(os.getcwd(), 'mypythonfile.py') 

process = subprocess.Popen([sys.executable,'-u',pythonfile], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 

print 'Process ID:', process.pid 

thread1 = printstd(process.stdout, 'stdout:') 
thread2 = printstd(process.stderr, 'stderr:') 

thread1.start() 
thread2.start() 

threads = [] 

threads.append(thread1) 
threads.append(thread2) 

for t in threads: 
    t.join() 

但是,我不確定這是否是線程安全的。

相關問題