如何打印和显示subprocess stdout和stderr输出而不失真?

时间:2011-10-11 16:27:01

标签: python subprocess

也许在以太中有人可以帮助我解决这个问题。 (我在SO上看到了很多类似的问题,但没有一个涉及标准输出和标准错误,也没有处理与我相似的情况,因此这个新问题。)

我有一个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包装器?但是,我只想在这个函数中使用包装器。

来自社区的任何其他想法?

我很感激帮助! :)

编辑:解决方案确实应该是跨平台的,但是如果你有不同的想法,请分享它们以保持头脑风暴。


对于我的另一个python子流程头部抓取器,请查看accounting for subprocess overhead in timing上的另一个问题。

3 个答案:

答案 0 :(得分: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上工作,它是可行的,但它更加困难。你必须:

答案 1 :(得分: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,

答案 2 :(得分: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()

但是,我不确定这是否是线程安全的。