实时subprocess.Popen通过stdout和PIPE

时间:2010-01-17 22:03:03

标签: python logging subprocess pipe popen

我正试图从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

8 个答案:

答案 0 :(得分:19)

你的翻译正在缓冲。在print语句后添加对sys.stdout.flush()的调用。

答案 1 :(得分:12)

实际上,真正的解决方案是直接将子流程的stdout重定向到流程的stdout。

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

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

communicate()是为了使调用阻塞直到子进程结束,否则它将直接转到下一行,程序可能会在子进程之前终止(尽管重定向到stdout仍然有效) ,即使你的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

答案 2 :(得分:11)

要“实时”输出,subprocess不合适,因为它无法击败其他进程的缓冲策略。这就是我总是推荐的原因,每当需要这样的“实时”输出抓取时(非常频繁的堆栈溢出问题!),改为使用pexpect(除了Windows之外的所有地方 - 在Windows上,wexpect )。

答案 3 :(得分:3)

删除正在合并输出的readlines()。 此外,您还需要强制执行行缓冲,因为大多数命令都会将输出缓冲到管道。有关详细信息,请参阅:http://www.pixelbeat.org/programming/stdio_buffering/

答案 4 :(得分: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的情况。

答案 5 :(得分:1)

cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE)
for line in cmd.stdout:
    print line.rstrip("\n")

答案 6 :(得分:0)

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

答案 7 :(得分:0)

如上所述,问题在于当没有终端连接到进程时stdio库缓冲了printf like语句。无论如何,在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