实时 subprocess.Popen 通过 stdout 和 PIPE

Posted

技术标签:

【中文标题】实时 subprocess.Popen 通过 stdout 和 PIPE【英文标题】:real time subprocess.Popen via stdout and PIPE 【发布时间】:2011-01-06 04:09:03 【问题描述】:

我正在尝试从subprocess.Popen 呼叫中获取stdout,尽管我很容易通过以下方式实现:

cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE)
for line in cmd.stdout.readlines():
    print line

我想“实时”获取stdout。使用上面的方法,PIPE正在等待抓取所有stdout然后返回。

因此,出于记录目的,这不符合我的要求(例如,当它发生时“查看”正在发生的事情)。

有没有办法在运行时逐行获取stdout?或者这是subprocess 的限制(必须等到PIPE 关闭)。

编辑 如果我将readlines() 切换为readline(),我只会得到stdout 的最后一行(不理想):

In [75]: cmd = Popen('ls -l', shell=True, stdout=PIPE)
In [76]: for i in cmd.stdout.readline(): print i
....: 
t
o
t
a
l

1
0
4

【问题讨论】:

复制:***.com/questions/1822237/… 相关:Python: read streaming input from subprocess.communicate() 使用readline(),您将得到第一行,而不是最后一行。您必须反复调用readline() 才能获取每一行。 相关:Python subprocess readlines() hangs 【参考方案1】:

如前所述,当没有终端连接到进程时,问题在于 stdio 库对类似 printf 语句的缓冲。无论如何,在 Windows 平台上有一种解决方法。其他平台上可能也有类似的解决方案。

在 Windows 上,您可以在进程创建时强制创建一个新控制台。好消息是它可以保持隐藏状态,因此您永远看不到它(这是由子进程模块中的 shell=True 完成的)。

cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE, creationflags=_winapi.CREATE_NEW_CONSOLE, bufsize=1, universal_newlines=True)
for line in cmd.stdout.readlines():
    print line

一个稍微更完整的解决方案是您明确设置 STARTUPINFO 参数,以防止启动一个新的和不必要的 cmd.exe shell 进程,上面 shell=True 所做的。

class PopenBackground(subprocess.Popen):
    def __init__(self, *args, **kwargs):

        si = kwargs.get('startupinfo', subprocess.STARTUPINFO())
        si.dwFlags |= _winapi.STARTF_USESHOWWINDOW
        si.wShowWindow = _winapi.SW_HIDE

        kwargs['startupinfo'] = si
        kwargs['creationflags'] = kwargs.get('creationflags', 0) | _winapi.CREATE_NEW_CONSOLE
        kwargs['bufsize'] = 1
        kwargs['universal_newlines'] = True

        super(PopenBackground, self).__init__(*args, **kwargs)

process = PopenBackground(['ls', '-l'], stdout=subprocess.PIPE)
    for line in cmd.stdout.readlines():
        print line

【讨论】:

child 的缓冲策略无关紧要,只要您使用直到 EOF 才会返回的 .readlines()。请改用for line in iter(cmd.stdout.readline, b''):。 Read my answers to the questions I've linked above【参考方案2】:

其实真正的解决方案是直接将子进程的stdout重定向到你进程的stdout。

确实,使用您的解决方案,您只能同时打印 stdout,而不能同时打印 stderr。

import sys
from subprocess import Popen
Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate()

communicate() 是为了使调用阻塞直到子进程结束,否则它将直接转到下一行,并且您的程序可能会在子进程之前终止(尽管重定向到您的标准输出仍然有效,即使在您的 python 脚本关闭后,我对其进行了测试)。

这样,例如,您正在重定向 stdout 和 stderr,并且是绝对实时的。

例如,在我的情况下,我使用此脚本进行了测试 slow_cmd_output.sh:

#!/bin/bash

for i in 1 2 3 4 5 6; do sleep 5 && echo "$ith output" && echo "err output num $i" >&2; done

【讨论】:

注意:我知道这个帖子很旧,但我遇到了这个帖子,答案并不让我满意。找到答案后,我想我不妨把它贴出来:) 这有什么陷阱吗? @撤消【参考方案3】:

由于这是我几天来一直在寻找答案的问题,因此我想将其留给那些关注的人。虽然subprocess 确实无法对抗其他进程的缓冲策略,但在您使用subprocess.Popen 调用另一个 Python 脚本的情况下,您可以告诉它启动一个无缓冲的 python。

command = ["python", "-u", "python_file.py"]
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

for line in iter(p.stdout.readline, ''):
    line = line.replace('\r', '').replace('\n', '')
    print line
    sys.stdout.flush()

我还看到了一些案例,其中 popen 参数 bufsize=1universal_newlines=True 有助于暴露隐藏的 stdout

【讨论】:

您可以使用print line,(注意:逗号)来避免删除换行符(不需要replace('\n','')rstrip(b'\r\n'))。【参考方案4】:

您的解释器正在缓冲。在您的打印语句之后添加对 sys.stdout.flush() 的调用。

【讨论】:

太棒了!谢谢!这就是它!,我已经放弃了......这非常有效。再次感谢!呜呼! @alfredodeza:它不可能工作 .readlines() 在读取 所有输出 之前不会返回(直到 EOF 发生),因此添加 sys.stdout.flush() 不会改变任何东西。它与您的要求相矛盾:“有没有办法在 [子进程] 运行时逐行获取标准输出?”【参考方案5】:

要“实时”获得输出,subprocess 不适合,因为它无法击败其他进程的缓冲策略。这就是我一直建议的原因,每当需要这种“实时”输出抓取(关于堆栈溢出的一个常见问题!),改为使用pexpect(除 Windows 之外的任何地方——在 Windows 上,wexpect)。

【讨论】:

Expect 的行缓冲是我(最近)深爱的一个话题;你介意看看我的最新问题吗? @Tobu,当然,看过并回答(再次推荐 pexpect)。 winpexpect module 也可以在 Windows 上使用。 你也可以provide a pseudo-tty even while using subprocess module【参考方案6】:

删除合并输出的 readlines()。 此外,您还需要强制执行行缓冲,因为大多数命令将内部缓冲输出到管道。详情见:http://www.pixelbeat.org/programming/stdio_buffering/

【讨论】:

我浏览了链接,但我不清楚如何在 Python 中强制缓冲,你能澄清一下吗? 您对命令强制执行缓冲。 tail -f 默认为行缓冲区。对于 grep、sed 等,您需要将适当的选项传递给它们。还要注意新的 stdbuf 命令,它可以将行缓冲应用于任何使用 stdio 的命令【参考方案7】:
cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
    print line.rstrip("\n")

【讨论】:

罗杰,这仍在等待过程结束。我可以确认这种情况正在发生的方法是运行更长的过程,例如系统更新并将信息传递到日志。 10 秒运行过程中的所有行同时打印/写入日志 您遇到了缓冲区大小问题;使用我上面的代码并将命令更改为["find", "/"],您将在该过程结束之前看到输出。 print(line.rstrip("\n"))在Python3中得到了TypeError: a bytes-like object is required, not 'str',你有什么想法吗?【参考方案8】:

readlines 的调用正在等待进程退出。用 cmd.stdout.readline() 周围的循环替换它(注意单数),一切都应该很好。

【讨论】:

ths 只返回最后一行,而不是所有行:for i in cmd.stdout.readline(): print i ....: t o t a l 1 0 4 是的,我的错。正确答案是上面罗伯特·佩特的答案。 Robert Pate 给出了答案(至少搜索 Robert,一无所获)。虽然你的答案不正确,但你应该删除它......

以上是关于实时 subprocess.Popen 通过 stdout 和 PIPE的主要内容,如果未能解决你的问题,请参考以下文章

Subprocess.Popen:将 stdout 和 stderr 克隆到终端和变量

使用 subprocess.Popen 通过 SSH 或 SCP 发送密码

通过 subprocess.Popen 在 python 中执行 R 脚本

如何使用 subprocess.Popen 通过管道连接多个进程?

从 subprocess.Popen 调用“源”命令

将变量传递给 Subprocess.Popen