拦截子进程运行时的标准输出流

25

如果这是我的子进程:

import time, sys
for i in range(200):
    sys.stdout.write( 'reading %i\n'%i )
    time.sleep(.02)

这是控制和修改子进程输出的脚本:

import subprocess, time, sys

print 'starting'
    
proc = subprocess.Popen(
    'c:/test_apps/testcr.py',
    shell=True,
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE  )

print 'process created'

while True:
    #next_line = proc.communicate()[0]
    next_line = proc.stdout.readline()
    if next_line == '' and proc.poll() != None:
        break
    sys.stdout.write(next_line)
    sys.stdout.flush()
    
print 'done'

为什么readlinecommunicate会等到进程运行完毕才结束?有没有简单的方法可以实时传递(并修改)子进程的标准输出?

我在Windows XP上。


相关:如何刷新Python print的输出? - Piotr Dobrogost
2个回答

16

正如Charles已经提到的那样,问题是缓冲。当我编写一些SNMPd模块时,遇到了类似的问题,并通过将stdout替换为自动刷新版本来解决它。

我使用了以下代码,受ActiveState上的一些帖子的启发:

class FlushFile(object):
    """Write-only flushing wrapper for file-type objects."""
    def __init__(self, f):
        self.f = f
    def write(self, x):
        self.f.write(x)
        self.f.flush()

# Replace stdout with an automatically flushing version
sys.stdout = FlushFile(sys.__stdout__)

我不明白这与在每个sys.stdout.readline()之后调用sys.stdout.flush()有什么不同,这就是我所做的。我还尝试为子进程设置bufsize=0。 - Paul
9
需要在子进程中进行冲洗,而不是在父进程中。 - bobince
是的,在这个例子中,子进程也是一个Python脚本。所以要替换子进程中的stdout。在父进程中调用sys.stdout.flush()不会有任何作用。 - Kamil Kisiel
好的,我明白我在那里做了什么。当然,这个子进程只是一个示例。我的真正进程是一个巨大的编译FORTRAN代码,我无法访问源代码。在这种情况下,我只需要希望子进程没有缓冲输出?那么subprocess.Popen的bufsize参数是用来做什么的呢? - Paul
据我所知,应用程序代码决定了输出缓冲区的大小。我认为除非它是动态链接的并且您预加载了替换系统调用的库,否则您无法在外部执行任何操作。但这是一个巨大的黑客行为,超出了本问题的范围 :) - Kamil Kisiel
当输出通过标准C库时,可能会有可用的缓冲区调整 -- 现代版本的glibc可以使用stdbuf进行缓冲区配置调整: http://www.gnu.org/software/coreutils/manual/coreutils.html#stdbuf-invocation -- 尽管我不知道这是否对Fortran应用程序有任何好处。 - Charles Duffy

8
进程输出被缓冲。在更多的UNIX操作系统(或Cygwin)上,pexpect模块可用,它会背诵所有必要的咒语以避免与缓冲相关的问题。然而,这些咒语需要一个工作的pty模块,它在本机(非cygwin)win32 Python版本中不可用。
在你控制子进程的示例情况下,你可以让它在必要时调用sys.stdout.flush() -- 但对于任意的子进程,这个选项是不可用的。
请参见pexpect FAQ中的“为什么不只使用管道(popen())?”的问题

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接