在 C 或 Python 中使用 popen 绕过子进程输出的缓冲

问题描述 投票:0回答:3

我有一个关于 popen (以及所有相关函数)的一般性问题,适用于所有操作系统,当我编写 python 脚本或一些 c 代码并从控制台(win 或 linux)运行生成的可执行文件时,我可以立即看到过程的输出。但是,如果我运行与分叉进程相同的可执行文件,并将其 stdout 重定向到管道中,则输出会在某处缓冲,通常最多 4096 字节,然后才会写入父进程可以读取的管道。

以下 python 脚本将生成 1024 字节块的输出

import os, sys, time

if __name__ == "__main__":
     dye = '@'*1024
     for i in range (0,8):
        print dye
        time.sleep(1)

以下 python 脚本将执行前面的脚本,并在输出到达管道时立即逐字节读取输出

import os, sys, subprocess, time, thread

if __name__ == "__main__":
    execArgs = ["c:\\python25\\python.exe", "C:\\Scripts\\PythonScratch\\byte_stream.py"]

    p = subprocess.Popen(execArgs, bufsize=0, stdout=subprocess.PIPE)
    while p.returncode == None:
        data = p.stdout.read(1)
        sys.stdout.write(data)
        p.poll()

调整您的操作系统的路径。在此配置中运行时,输出不会以 1024 的块出现,而是以 4096 的块出现,尽管 popen 命令的缓冲区大小设置为 0(无论如何这是默认值)。谁能告诉我如何改变这种行为?有什么方法可以强制操作系统以与从控制台运行时相同的方式处理分叉进程的输出?即,只需通过没有缓冲?

python c buffer pipe
3个回答
17
投票

一般来说,标准 C 运行时库(或多或少代表每个系统上的几乎每个程序运行;-)检测 stdout 是否是终端;如果没有,它会缓冲输出(与未缓冲的输出相比,这可能会带来巨大的效率提升)。

如果您控制正在写入的程序,您可以(正如另一个答案所建议的那样)连续刷新标准输出,或者(如果可行的话更优雅)尝试强制标准输出不缓冲,例如通过使用

-u
命令行标志运行 Python:

-u     : unbuffered binary stdout and stderr (also PYTHONUNBUFFERED=x)
         see man page for details on internal buffering relating to '-u'

(手册页添加的是对 stdin 的提及以及二进制模式的问题)。

如果您不能或不想接触正在写入的程序,则正在读取的程序上的

-u
或类似内容不太可能有帮助(最重要的缓冲是发生在编写器的标准输出上的缓冲,而不是发生在编写器的标准输出上的缓冲)读者标准输入上的那个)。另一种方法是通过
pty
标准库模块或更高级别的第三方 pexpect 模块(或者,对于 Windows,其端口 wexpect)。


1
投票

这是正确的,并且适用于 Windows 和 Linux(可能还有其他系统),带有

popen()
fopen()
。如果您希望在 4096 字节之前分派输出缓冲区,请使用
fflush()
(在 C 上)或
sys.stdout.flush()
(Python)。


0
投票

在 C/C++ 中,popen 正在读取 child 进程。可以调用setvbuf函数:

#include <stdio.h>
...
int main(){
  setvbuf(stdout,NULL,_IONBF,0);
  ...
}

这会将标准输出设置为非缓冲,并且您的输出将正常工作。通常我会在 main() 过程开始时执行此操作。

我还没有找到任何方法可以从读取管道的主机进程中执行此操作。据推测,必须有一些 fcntl 或 stty 函数来欺骗子进程,使其认为它在终端中。如果有人知道答案,我很想知道。

© www.soinside.com 2019 - 2024. All rights reserved.