【发布时间】:2011-02-12 12:53:48
【问题描述】:
我正在使用 Python 的 subprocess.communicate() 从运行大约一分钟的进程中读取标准输出。
如何以流方式打印出该进程的stdout 的每一行,以便我可以看到生成的输出,但在继续之前仍会阻止进程终止?
subprocess.communicate() 似乎一次性提供所有输出。
【问题讨论】:
标签: python subprocess
我正在使用 Python 的 subprocess.communicate() 从运行大约一分钟的进程中读取标准输出。
如何以流方式打印出该进程的stdout 的每一行,以便我可以看到生成的输出,但在继续之前仍会阻止进程终止?
subprocess.communicate() 似乎一次性提供所有输出。
【问题讨论】:
标签: python subprocess
如果您想要一种非阻塞方法,请不要使用process.communicate()。如果将subprocess.Popen() 参数stdout 设置为PIPE,则可以从process.stdout 读取并检查进程是否仍在使用process.poll() 运行。
请注意,我认为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 循环可能会在您读取所有数据之前结束。
您可以通过这种方式在标准输出中捕获余数:
output = proc.communicate()[0]
print output,
【讨论】:
while proc.poll() is None: time.sleep(0) 或类似的东西有关。基本上-您需要确保输出换行符是该过程执行的最后一件事(因为您不能给解释器时间再次循环),或者您需要做一些“花哨的”事情。
我相信以流方式从进程中收集输出的最简单方法是这样的:
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() 可能更适合减少最大内存使用量 - 传递给它的数字是任意的,但排除它会导致一次读取整个管道输出,这可能是不可取的。
【讨论】:
data = proc.stdout.read() 阻塞,直到 all 数据被读取。您可能会将它与可以更早返回的os.read(fd, maxsize) 混淆(只要有任何数据可用)。
read(),那么它工作正常,同样readline() 工作正常,只要最大行长度是合理的。相应地更新了我的答案。
在子进程刷新其标准输出缓冲区后,逐行获取子进程的输出:
#!/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。
如果子进程的标准输出在非交互模式下使用块缓冲而不是行缓冲(这会导致输出延迟,直到子进程的缓冲区已满或被子进程显式刷新),那么您可以尝试强制使用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 那样输出子进程的字节串; Python 3 使用文本模式(cmd 的输出使用locale.getpreferredencoding(False) 编码解码)。
【讨论】:
b'' 是 Python 2.7 和 Python 3 中的 bytes 文字。
bufsize=1 如果你也写(使用p.stdin)到子进程,它可以帮助避免死锁,同时执行交互式(pexpect-like)交换——假设子进程本身没有缓冲问题。如果您只是阅读,那么正如我所说,差异仅在于性能:如果不是这样,那么您能否提供一个最小的完整代码示例来显示它?
stderr=subprocess.STDOUT 传递给Popen())。另请参阅,threading 或 asyncio solutions 链接在那里。
stdout=PIPE 没有捕获输出(您仍然可以在屏幕上看到它),那么您的程序可能会打印到 stderr 或直接打印到终端。要合并 stdout&stderr,请传递 stderr=subprocess.STDOUT(请参阅我之前的评论)。要捕获直接打印到您的 tty 的输出,您可以use pexpect, pty solutions.。这是more complex code example。
如果您只是想实时传递输出,很难比这更简单:
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 会失败。 stderr 已被替换(例如,用于捕获测试中的输出)。
【讨论】:
sys.stdout 或sys.stderr 被替换为没有真正fileno() 的类文件对象,它将不起作用。如果sys.stdout、sys.stderr不被替换,那就更简单了:subprocess.check_call(args)。
call() 而不是check_call(),除非我想要CalledProcessError。
python -mthis: “错误永远不应该默默地传递。除非明确地沉默。” 这就是为什么示例代码应该更喜欢check_call() call().
call()ing 在非错误条件下返回非零错误代码,因为它们很糟糕。所以在我们的例子中,非零错误代码实际上并不是错误。
grep 之类的程序即使没有错误也可能返回非零退出状态——它们是异常。默认情况下,退出状态为零表示成功。
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'))
【讨论】:
shlex.split(myCommand) 而不是myCommand.split()。它也尊重引用参数中的空格。
添加另一个 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)
【讨论】: