我正在使用Python的subprocess.communicate()
从运行大约一分钟的进程中读取stdout。
如何以流式方式打印出该进程的stdout
的每一行,以便我可以看到生成的输出,但在继续之前仍然阻止进程终止?
subprocess.communicate()
似乎可以立即提供所有输出。
答案 0 :(得分:130)
一旦子进程刷新其stdout缓冲区,就要逐行获取子进程的输出:
#!/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()
用于在写入解决方法the read-ahead bug in Python 2后立即读取行。
如果子进程'stdout在非交互模式下使用块缓冲而不是行缓冲(导致输出延迟,直到子缓冲区已满或由孩子明确刷新),那么你可以尝试强制使用pexpect
, pty
modules或unbuffer
, stdbuf
, script
utilities的无缓冲输出,请参阅Q: Why not just use a pipe (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不同,它按原样输出子进程'bytestrings; Python 3使用文本模式(cmd的输出使用locale.getpreferredencoding(False)
编码进行解码)。
答案 1 :(得分:41)
请注意,我认为J.F. Sebastian's method (below)更好。
这是一个简单的例子(没有检查错误):
import subprocess
proc = subprocess.Popen('ls',
shell=True,
stdout=subprocess.PIPE,
)
while proc.poll() is None:
output = proc.stdout.readline()
print output,
如果ls
结束得太快,那么while循环可能会在您读取所有数据之前结束。
你可以用这种方式捕获stdout中的剩余部分:
output = proc.communicate()[0]
print output,
答案 2 :(得分:5)
我认为以流媒体方式从流程中收集输出的最简单方法是这样的:
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()
函数应该只在进程终止后在EOF上返回一个空字符串 - 否则如果没有要读取的内容它会阻塞(readline()
包含换行符,所以在空行上,它返回“\ n”)。这避免了在循环之后需要进行尴尬的最终communicate()
调用。
对于具有很长行read()
的文件,最好减少最大内存使用量 - 传递给它的数字是任意的,但排除它会导致一次读取整个管道输出,这可能是不可取的。< / p>
答案 3 :(得分:3)
如果您想要非阻止方法,请不要使用process.communicate()
。如果您将subprocess.Popen()
参数stdout
设置为PIPE
,则可以从process.stdout
进行阅读,并使用process.poll()
检查流程是否仍在运行。
答案 4 :(得分:2)
如果您只是想通过实时传递输出,那么很难比这简单:
import subprocess
# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])
请参阅docs for subprocess.check_call()。
如果您需要处理输出,请确保循环。但如果你不这样做,那就保持简单。
编辑: J.F. Sebastian指出stdout和stderr参数的默认值都传递给sys.stdout和sys.stderr,如果是sys.stdout,则会失败sys.stderr已被替换(例如,用于捕获测试中的输出)。
答案 5 :(得分:1)
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:
print(p.stderr.readline().rstrip('\r\n'))
答案 6 :(得分:0)
添加具有一些小的更改的另一个python3解决方案:
with
构造时,我无法获得退出代码)import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
# Run a shell command, streaming output to STDOUT in real time
# Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
for line in p.stdout:
sys.stdout.write(line)
p.wait()
exit_code = p.returncode
if exit_code != 0 and fail_on_error:
raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
return(exit_code)