在管道中强制标准输出的行缓冲

Posted

技术标签:

【中文标题】在管道中强制标准输出的行缓冲【英文标题】:Force line-buffering of stdout in a pipeline 【发布时间】:2012-07-05 10:15:17 【问题描述】:

通常,stdout 是行缓冲的。换句话说,只要您的 printf 参数以换行符结尾,您就可以期望该行会立即打印。当使用管道重定向到 tee 时,这似乎不成立。

我有一个 C++ 程序a,它输出字符串,总是\n-终止,到stdout

当它自行运行 (./a) 时,一切都按预期在正确的时间正确打印。但是,如果我将它通过管道传输到 tee (./a | tee output.txt),它在退出之前不会打印任何内容,这违背了使用 tee 的目的。

我知道我可以通过在 C++ 程序中的每次打印操作后添加 fflush(stdout) 来修复它。但是有没有更清洁、更简单的方法?有没有我可以运行的命令,例如,它会强制 stdout 进行行缓冲,即使在使用管道时也是如此?

【问题讨论】:

【参考方案1】:

你可以试试stdbuf

$ stdbuf --output=L ./a | tee output.txt

手册页的(大)部分:

  -i, --input=MODE   adjust standard input stream buffering
  -o, --output=MODE  adjust standard output stream buffering
  -e, --error=MODE   adjust standard error stream buffering

If MODE is 'L' the corresponding stream will be line buffered.
This option is invalid with standard input.

If MODE is '0' the corresponding stream will be unbuffered.

Otherwise MODE is a number which may be followed by one of the following:
KB 1000, K 1024, MB 1000*1000, M 1024*1024, and so on for G, T, P, E, Z, Y.
In this case the corresponding stream will be fully buffered with the buffer
size set to MODE bytes.

请记住这一点:

NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does
for e.g.) then that will override corresponding settings changed by 'stdbuf'.
Also some filters (like 'dd' and 'cat' etc.) dont use streams for I/O,
and are thus unaffected by 'stdbuf' settings.

你不是在tee 上运行stdbuf,而是在a 上运行它,所以这不会影响你,除非你在a 的流中设置缓冲a'来源。

另外,stdbuf 不是 POSIX,而是 GNU-coreutils 的一部分。

【讨论】:

谢谢,但这似乎在 OS X 上不可用(问题标记为 osx-lion)。 @houbysoft - 我很确定 GNU 工具可以安装在 OS X 上 @jordanm:也许吧,但是安装整个 GNU 工具似乎有点矫枉过正...... 赞成这个答案,因为 stdbuf 已经在我们使用的 Centos Linux 发行版上可用,而 unbuffer 不可用。谢谢! 对于 python 脚本 stdbuf 将不起作用,但您可以使用 -u 禁用 python 端的缓冲:python3 -u a.py | tee output.txt【参考方案2】:

试试unbuffer,它是expect 包的一部分。您可能已经在您的系统上安装了它。

在你的情况下,你会这样使用它:

./a | unbuffer -p tee output.txt

-p 用于管道模式,其中 unbuffer 从标准输入读取并将其传递给其余参数中的命令)

【讨论】:

谢谢,这行得通,虽然我必须自己编译expect,因为unbuffer 似乎默认不包含在 OS X 中。 我通过 brew 将它安装在我的 mac (10.8.5) 上:brew install expect --with-brewed-tk FWIW,因为unbuffer有点混乱,相关结构是unbuffer commands with pipes/tee 我认为它应该是“unbuffer ./a | tee output.txt”——它不是需要取消缓冲的 tee。至少,对于类似的问题,这对我有用。 如果您阅读手册页,我认为正确的命令是unbuffer ./a | tee output.txt。这就是在 RPi 下使用 bash 和 tmux 对我有用的方法。【参考方案3】:

您也可以尝试使用script 命令在伪终端中执行您的命令(这应该强制将行缓冲输出到管道)!

script -q /dev/null ./a | tee output.txt     # Mac OS X, FreeBSD
script -c "./a" /dev/null | tee output.txt   # Linux

请注意script 命令不会传播回包装命令的退出状态。

【讨论】:

script -t 1 /path/to/outputfile.txt ./a 非常适合我的用例。它将所有输出实时流式传输到outputfile.txt,同时还将其打印到您的shell 的标准输出。不需要使用tee script from util-linux 和 BSD 都提供了-e 选项来返回执行命令的退出状态。【参考方案4】:

您可以使用 stdio.h 中的 setlinebuf。

setlinebuf(stdout);

这应该将缓冲更改为“行缓冲”。

如果您需要更大的灵活性,可以使用 setvbuf。

【讨论】:

我想知道为什么这个解决方案的赞成票这么少。这是唯一不会给调用者带来负担的解决方案。 请注意,这不是标准的 C(甚至是 POSIX)。使用setvbuf(stdout, NULL, _IOLBF, 0) 可能会更好,这完全是等价的。 这解决了我在 OS X Catalina 上的问题,它使用了一个 C++ 程序,该程序正在 printf()ing 并且我正在管道到 tee 但仅在程序完成后才看到输出。【参考方案5】:

@Paused until further notice 答案中的expect 包中的unbuffer 命令对我来说并不像它呈现的方式那样工作。

而不是使用:

./a | unbuffer -p tee output.txt

我不得不使用:

unbuffer -p ./a | tee output.txt

-p 用于管道模式,其中 unbuffer 从标准输入读取并将其传递给其余参数中的命令)

expect 包可以安装在:

    MSYS2 与 pacman -S expect 带有brew install expect 的Mac OS

更新

我最近在 shell 脚本中遇到了 python 的缓冲问题(尝试将时间戳附加到其输出时)。解决方法是通过这种方式将-u 标志传递给python

    run.shpython -u script.py unbuffer -p /bin/bash run.sh 2>&1 | tee /dev/tty | ts '[%Y-%m-%d %H:%M:%S]' >> somefile.txt 此命令将在输出中添加时间戳,并同时将其发送到文件和标准输出。 ts 程序(时间戳)可以与moreutils 包一起安装。

更新 2

最近,grep 缓冲输出也有问题,当我在 grep 上使用参数 grep --line-buffered 来停止缓冲输出时。

【讨论】:

我也遇到了同样的情况。这样,它就奏效了。【参考方案6】:

如果您改用 C++ 流类,则每个 std::endl 都是 隐式刷新。使用C风格的打印,我认为你建议的方法(fflush())是唯一的方法。

【讨论】:

不幸的是,这不是真的。即使在使用 std::endl 或 std::flush 时,您也可以使用 c++ std::cout 观察到相同的行为。缓冲发生在顶部,Linux 中最简单的解决方案似乎是 setlinebuf(stdout);当您是程序的作者并在无法更改源代码时使用上述其他解决方案时,作为 main() 的第一行。 @oxygene 这不是真的。我试过了,当管道连接到 tee 时,endl 确实刷新了缓冲区(与 printf 不同)。代码:#include <iostream> #include <unistd.h> int main(void) std::cout << "1" << std::endl; sleep(1); std::cout << "2" << std::endl; 。 endl 总是按照此处定义的方式刷新缓冲区:en.cppreference.com/w/cpp/io/manip/endl

以上是关于在管道中强制标准输出的行缓冲的主要内容,如果未能解决你的问题,请参考以下文章

C中的管道,用于读取标准输入的缓冲区

Linux C程序获取本机可用IP地址 && Linux C程序获取system()函数的标准输出到char *缓冲中

输入输出重定向及管道

标准 I/O 和管道

在进程通信中管道作为标准输入/标准输出。

sed 命令编辑文本