将子进程的输出同时发送到PIPE和标准输出

13

我找到了一些看起来和我的问题类似的问题,但它们没有给出我可以使用的解决方案(最接近的是:subprocess output to stdout and to PIPE)。

问题是:我想使用subprocess启动一个耗时较长的进程。在运行命令后,我需要解析stdout和stderr的输出。

目前我是这样做的:

p = subprocess.Popen( command_list, stdout=subprocess.PIPE, 
    stderr=subprocess.PIPE )
out, error_msg = p.communicate()
print out + "\n\n" + error_msg

#next comes code in which I check out and error_msg

但这种方法的缺点是用户在进程运行时无法看到输出结果,只有在结尾才会打印输出。

是否有一种方法可以在命令运行时打印输出(就像我没有使用stdout/stderr=subprocess.PIPE一样),并且最终仍然能够通过p.communicate获得输出?

注意:我目前正在开发Python 2.5上(这个Python版本的旧软件发布版)。


2个回答

8
这段代码曾在类似情况下对我有所帮助:
process = subprocess.Popen(cmd, bufsize=1, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
for line in iter(process.stdout.readline, ''):
    print line,
    sys.stdout.flush() # please see comments regarding the necessity of this line 
process.wait()
errcode = process.returncode

@tripleee:我发现如果我们假设python进程和子进程具有相同的缓冲策略(如果它们都是基于stdio的话,这很可能),那么sys.stdout.flush()在这里是不必要的。也就是说,如果在终端中运行python脚本(没有重定向),那么stdout已经是行缓冲的了--不需要sys.stdout.flush()。如果输出被重定向到文件中(尽管OP要求不是这样),那么flush()会不必要地减慢打印速度(当输出为文件时通常使用块缓冲,每行刷新使其变慢)。 - jfs
只有在给出“--line-buffering”选项(如“grep”)时,我才会显式调用flush() - jfs
谢谢。这几乎解决了我的问题。正如J.F. Sebastian所说,我需要分别捕获stdout和stderr(并同时打印它们)。但这很有帮助。 - Nemelis
1
@Nemelis:不幸的是,分别捕获stdout/stderr会使代码变得非常复杂:你需要线程或select()或其他异步I/O工具。请参见我上面链接的问题。 - jfs
@J.F.Sebastian:谢谢。我会去看看的。已经发现单独捕获它们并不容易。 - Nemelis
显示剩余3条评论

1
这是一个Python函数,它监视命令的标准输出和错误输出,并在成功完成时返回输出,否则会引发异常。
import subprocess
def execute_command(cmd):
    """
    Execute and watch shell command, and return its output
    """
    output = ''
    with subprocess.Popen(
            cmd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            encoding='utf-8',
            errors='replace'
        ) as p:
            while True:
                line = p.stdout.readline()
                if line != '':
                    output += (line + "\n")
                    print(line)
                elif p.poll() != None:
                    break
            sys.stdout.flush()
            if (p.returncode == 0):
                return output
            else:
                raise Exception(cmd, p.returncode, output)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接