从subprocess.communicate()读取流式输入
我正在使用Python的subprocess.communicate()
来读取一个运行大约一分钟的进程的标准输出(stdout)。
我想知道怎么才能实时打印出这个进程的每一行stdout
,这样我就可以看到输出是如何生成的,但在进程结束之前我又希望能够暂停继续执行。
看起来subprocess.communicate()
是一次性给出所有的输出。
7 个回答
我认为,收集一个进程输出的最简单方法就是这样:
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()
可能更好,这样可以减少内存的使用量。传给它的数字是随便的,但如果不传这个数字,它会一次性读取整个管道的输出,这通常不是我们想要的。
要想实时获取子进程的输出,每当子进程刷新它的标准输出时,就可以逐行读取:
#!/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。
如果子进程的标准输出在非交互模式下使用的是块缓冲,而不是行缓冲(这会导致输出延迟,直到子进程的缓冲区满了或者被子进程明确刷新),那么你可以尝试强制使用无缓冲输出,方法是使用 pexpect
、pty
模块,或者 unbuffer
、stdbuf
、script
工具,具体可以参考 问:为什么不直接使用管道(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)
编码进行解码)。
请注意,我认为 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,