在进程运行时持续打印子进程输出

329 投票
17 回答
358021 浏览
提问于 2025-04-16 08:24

为了从我的Python脚本中启动程序,我使用了以下方法:

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)

所以当我执行一个进程,比如 Process.execute("mvn clean install") 时,我的程序会一直等到这个进程完成,然后才会显示我的程序的完整输出。如果这个进程需要很长时间才能完成,这就很烦人。

我能不能让我的程序在进程完成之前,逐行输出这个进程的结果,像是通过一个循环不断检查输出呢?

我找到了一篇可能相关的文章,在这里

17 个回答

90

好的,我成功地在不使用线程的情况下解决了这个问题(如果有建议说使用线程会更好,我很乐意听听),我用了这个问题中的一段代码:在子进程运行时拦截它的标准输出

def execute(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    # Poll process for new output until finished
    while True:
        nextline = process.stdout.readline()
        if nextline == '' and process.poll() is not None:
            break
        sys.stdout.write(nextline)
        sys.stdout.flush()

    output = process.communicate()[0]
    exitCode = process.returncode

    if (exitCode == 0):
        return output
    else:
        raise ProcessException(command, exitCode, output)
147

如果你想在Python 3中实时打印子进程的输出,每当它的输出缓冲区被刷新时,可以使用以下代码:

from subprocess import Popen, PIPE, CalledProcessError

with Popen(cmd, stdout=PIPE, bufsize=1, universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='') # process line here

if p.returncode != 0:
    raise CalledProcessError(p.returncode, p.args)

注意:你不需要使用 p.poll(),因为循环会在到达文件结束符(eof)时自动结束。而且你也不需要使用 iter(p.stdout.readline, ''),因为Python 3已经修复了提前读取的问题。

另外,你可以查看这个链接,了解更多关于如何从子进程获取流式输入的信息:Python: read streaming input from subprocess.communicate().

375

你可以使用 iter 这个功能,来处理命令输出的每一行,马上就能用到:lines = iter(fd.readline, "")。下面是一个完整的例子,展示了一个常见的用法(感谢 @jfs 的帮助):

from __future__ import print_function # Only Python 2.x
import subprocess

def execute(cmd):
    popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
    for stdout_line in iter(popen.stdout.readline, ""):
        yield stdout_line 
    popen.stdout.close()
    return_code = popen.wait()
    if return_code:
        raise subprocess.CalledProcessError(return_code, cmd)

# Example
for path in execute(["locate", "a"]):
    print(path, end="")

撰写回答