23

我试图stdout从电话中subprocess.Popen接听电话,尽管我很容易通过以下方式实现这一目标:

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
4

8 回答 8

22

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

于 2010-02-26T21:41:31.327 回答
17

实际上,真正的解决方案是直接将子进程的标准输出重定向到你进程的标准输出。

实际上,使用您的解决方案,您只能同时打印 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 "${i}th output" && echo "err output num ${i}" >&2; done
于 2014-06-09T19:30:27.813 回答
11

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

于 2010-01-18T01:30:31.473 回答
3

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

我还看到了一些公开争论的案例,bufsize=1universal_newlines=True帮助揭露了隐藏的stdout.

于 2013-05-06T18:26:33.687 回答
3

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

于 2010-01-17T22:31:11.330 回答
1
cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
    print line.rstrip("\n")
于 2010-01-17T22:13:24.030 回答
0

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

于 2010-01-17T22:09:27.273 回答
0

如前所述,当没有终端连接到进程时,问题在于 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 参数,以防止启动 shell=True 上面所做的新的和不必要的 cmd.exe shell 进程。

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
于 2015-09-12T12:43:06.750 回答