【问题标题】:How can I print and display subprocess stdout and stderr output without distortion?如何打印和显示子进程 stdout 和 stderr 输出而不失真?
【发布时间】:2011-12-05 11:20:21
【问题描述】:

也许有人在外面可以帮助我解决这个问题。 (我在 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 上的另一个问题。

【问题讨论】:

  • 你可能想看看 pexpect 之类的东西。
  • 为什么不直接创建一个StringIO并将同一个实例传递给子进程的stdout和stderr?
  • @NathanErnst:因为那行不通。 stdoutstderr 必须是真实的操作系统级文件描述符。
  • @Sven Marnach,我刚刚检查了文档,stderr 可以设置为 STDOUT,这将重定向,因此您可以将 stdout 设置为 PIPE,然后从 @987654339 中读取@.

标签: python subprocess


【解决方案1】:

使用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 上工作,这是可行的,但要困难得多。您必须:

【讨论】:

  • 这也可以在windows中完成吗?我看起来 fcntl 只是 unix,select 也有一些规定。我应该在我的问题中提到解决方案需要跨平台。我会补充一点。不过,这仍然很酷!谢谢!
  • 我注意到 fcntl 仅适用于类 Unix 平台。如果他在一个 windows 盒子上,或者希望这是不可知的,那么这个解决方案将不起作用。
  • 我明白了。好吧,您的 unix-esque 解决方案效果很好,我想我会将事物的 windows 部分保存为一个下雨的下午项目。感谢您的帮助!
  • 遗憾的是,windows 端的支持不如 linux,但我想这就是野兽的本性......一个大而多毛的闭源非标准管道野兽。
  • 由于poll() is not None 的较早返回,它是否会丢失一些输出? EAGAIN 上更好的返回值可以是 None,以允许对空字符串进行 eof 检测。顺便说一句,如果有一些平台支持select 管道超时但不支持fcntl(NONBLOCK),那么os.read(size) 可以用于读取可用输出(它可能小于size)。虽然我不知道任何这样的平台。
【解决方案2】:

结合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,

【讨论】:

  • 如果你要结合标准错误和标准输出,更常见的方法是使用stderr=subprocess.STDOUT
【解决方案3】:

当我测试它时,似乎 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()

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

【讨论】:

    猜你喜欢
    • 2018-07-31
    • 2023-04-06
    • 2021-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    • 2011-07-07
    • 2020-06-23
    相关资源
    最近更新 更多