从subprocess.communicate()读取流式输入

99 投票
7 回答
106649 浏览
提问于 2025-04-15 22:02

我正在使用Python的subprocess.communicate()来读取一个运行大约一分钟的进程的标准输出(stdout)。

我想知道怎么才能实时打印出这个进程的每一行stdout,这样我就可以看到输出是如何生成的,但在进程结束之前我又希望能够暂停继续执行。

看起来subprocess.communicate()是一次性给出所有的输出。

7 个回答

6

我认为,收集一个进程输出的最简单方法就是这样:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

在进程结束后,readline()read() 函数应该只在文件结束时返回一个空字符串,否则如果没有可读内容,它会一直等待(readline() 会把换行符也算上,所以在空行时,它返回的是 "\n")。这样就不需要在循环结束后再调用一个麻烦的 communicate()

对于那些行很长的文件,使用 read() 可能更好,这样可以减少内存的使用量。传给它的数字是随便的,但如果不传这个数字,它会一次性读取整个管道的输出,这通常不是我们想要的。

183

要想实时获取子进程的输出,每当子进程刷新它的标准输出时,就可以逐行读取:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter() 被用来在行被写入时立即读取,这样可以绕过 Python 2中的预读bug

如果子进程的标准输出在非交互模式下使用的是块缓冲,而不是行缓冲(这会导致输出延迟,直到子进程的缓冲区满了或者被子进程明确刷新),那么你可以尝试强制使用无缓冲输出,方法是使用 pexpectpty模块,或者 unbufferstdbufscript工具,具体可以参考 问:为什么不直接使用管道(popen())?


以下是Python 3的代码:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

注意:与Python 2不同,Python 3会将子进程的字节串作为文本输出;Python 3使用文本模式(命令的输出会使用 locale.getpreferredencoding(False) 编码进行解码)。

49

请注意,我认为 J.F. Sebastian的方法(下面)更好。


这里有一个简单的例子(没有检查错误):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

如果 ls 命令执行得太快,那么 while 循环可能在你还没读完所有数据之前就结束了。

你可以通过这种方式捕获剩下的数据:

output = proc.communicate()[0]
print output,

撰写回答