从 subprocess.communicate() 读取流输入

Posted

技术标签:

【中文标题】从 subprocess.communicate() 读取流输入【英文标题】:Read streaming input from subprocess.communicate() 【发布时间】:2011-02-12 12:53:48 【问题描述】:

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

如何以流方式打印出该进程的stdout 的每一行,以便我可以看到生成的输出,但在继续之前仍会阻止进程终止?

subprocess.communicate() 似乎一次性提供所有输出。

【问题讨论】:

相关:Getting realtime output using subprocess 【参考方案1】:

如果您想要一种非阻塞方法,请不要使用process.communicate()。如果将subprocess.Popen() 参数stdout 设置为PIPE,则可以从process.stdout 读取并检查进程是否仍在使用process.poll() 运行。

【讨论】:

non-blocking approach is not straightforward【参考方案2】:

请注意,我认为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,

【讨论】:

这个方案是否会成为python文档所指的缓冲区阻塞问题的牺牲品? @Heinrich,缓冲区阻塞问题我不太了解。我相信(仅通过谷歌搜索)只有在 while 循环内不从 stdout(和 stderr?)读取时才会出现此问题。所以我觉得上面的代码还可以,但是不能肯定。 这实际上确实遇到了阻塞问题,几年前我一直没有解决 readline 会阻塞的麻烦,直到它得到一个换行符,即使 proc 已经结束。我不记得解决方案,但我认为它与在工作线程上进行读取以及循环 while proc.poll() is None: time.sleep(0) 或类似的东西有关。基本上-您需要确保输出换行符是该过程执行的最后一件事(因为您不能给解释器时间再次循环),或者您需要做一些“花哨的”事情。 @Heinrich:Alex Martelli 在这里写了关于如何避免僵局的文章:***.com/questions/1445627/… 缓冲区阻塞比有时听起来更简单:父块等待子块退出 + 子块等待父块读取并释放通信管道中已满的一些空间 = 死锁。就是这么简单。管道越小,发生的可能性就越大。【参考方案3】:

我相信以流方式从进程中收集输出的最简单方法是这样的:

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() 工作正常,只要最大行长度是合理的。相应地更新了我的答案。【参考方案4】:

在子进程刷新其标准输出缓冲区后,逐行获取子进程的输出:

#!/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'' 是什么意思? b'' 是 Python 2.7 和 Python 3 中的 bytes 文字。 @JinghaoShi: bufsize=1 如果你也(使用p.stdin)到子进程,它可以帮助避免死锁,同时执行交互式(pexpect-like)交换——假设子进程本身没有缓冲问题。如果您只是阅读,那么正如我所说,差异仅在于性能:如果不是这样,那么您能否提供一个最小的完整代码示例来显示它? @ealeon:是的。它需要可以read stdout/stderr concurrently 的技术,除非您将stderr 合并到stdout 中(通过将stderr=subprocess.STDOUT 传递给Popen())。另请参阅,threading 或 asyncio solutions 链接在那里。 @saulspatz 如果stdout=PIPE 没有捕获输出(您仍然可以在屏幕上看到它),那么您的程序可能会打印到 stderr 或直接打印到终端。要合并 stdout&stderr,请传递 stderr=subprocess.STDOUT(请参阅我之前的评论)。要捕获直接打印到您的 tty 的输出,您可以use pexpect, pty solutions.。这是more complex code example。【参考方案5】:

如果您只是想实时传递输出,很难比这更简单:

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.stdoutsys.stderr 被替换为没有真正fileno() 的类文件对象,它将不起作用。如果sys.stdoutsys.stderr不被替换,那就更简单了:subprocess.check_call(args) 谢谢!我意识到替换 sys.stdout/stderr 的变幻莫测,但不知何故从未意识到,如果你省略参数,它会将 stdout 和 stderr 传递到正确的位置。我喜欢call() 而不是check_call(),除非我想要CalledProcessError python -mthis: “错误永远不应该默默地传递。除非明确地沉默。” 这就是为什么示例代码应该更喜欢check_call() call(). 嘿。我结束的很多程序call()ing 在非错误条件下返回非零错误代码,因为它们很糟糕。所以在我们的例子中,非零错误代码实际上并不是错误。 是的。有诸如grep 之类的程序即使没有错误也可能返回非零退出状态——它们是异常。默认情况下,退出状态为零表示成功。【参考方案6】:
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()。它也尊重引用参数中的空格。【参考方案7】:

添加另一个 python3 解决方案并进行一些小改动:

    允许您捕获 shell 进程的退出代码(我在使用 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)

【讨论】:

以上是关于从 subprocess.communicate() 读取流输入的主要内容,如果未能解决你的问题,请参考以下文章

为啥在不同线程中调用 asyncio subprocess.communicate 会挂起?

Python BUG 或者我不明白编码是如何工作的? len、find 和 re.search 在没有空的、成功的 subprocess.communicate() 执行结果的情况下啥也不做

从 NIB 与从代码加载自定义滑块:从代码加载时不存在子视图

如何从其他面板从 JTextField 获取输入

从PRISM开始学WPFMVVMViewModel?

在 python 中,为啥从数组读取比从列表读取慢?