关闭管道中的缓冲


395

我有一个脚本,它调用两个命令:

long_running_command | print_progress

long_running_command打印进度,但我不开心吧。我print_progress用来使其更美观(即,我将进度打印在一行中)。

问题:将管道连接到stdout还会激活4K缓冲区,到漂亮的打印程序不会得到任何东西……没有任何东西……没有任何东西 ……:)

如何禁用4K缓冲区long_running_command(不,我没有源)?


1
因此,当您在不进行管道传输的情况下运行long_running_command时,您可以看到进度更新正确,但是在管道传输得到缓冲时呢?

1
是的,就是这样。
亚伦·迪古拉

20
几十年来,一直无法采用简单的方法来控制缓冲。例如,请参阅:marc.info/?l=glibc-bug&m=98313957306297&w=4基本上说“我不能为此感到惊讶,这里有些拍手可证明我的立场是正确的”


1
实际上,不是在等待足够数据时导致延迟的管道是stdio。管道确实具有容量,但是一旦有任何数据写入管道,就可以立即在另一端读取数据。
山姆·沃特金斯

Answers:


254

您可以使用unbuffer命令(expect软件包附带的命令),例如

unbuffer long_running_command | print_progress

unbuffer连接到long_running_command经由一伪终端(PTY),这使得该系统把它作为一个互动的过程,因此不使用在管道即延迟的可能原因的4 KIB缓冲。

对于更长的管道,您可能必须取消缓冲每个命令(最后一个命令除外),例如

unbuffer x | unbuffer -p y | z

3
实际上,通常使用pty连接到交互过程是正确的。

15
当流水传输对unbuffer的调用时,应使用-p参数,以便unbuffer从stdin读取。

26
注意:在debian系统上,这称为expect_unbuffer,并且在expect-dev软件包中,而不是在expect软件包中
bdonlan

4
@bdonlan:至少在Ubuntu(基于debian)上,同时expect-dev提供unbufferexpect_unbuffer(前者是后者的符号链接)。自expect 5.44.1.14-12009年以来,这些链接可用。
jfs 2013年

1
注意:在Ubuntu 14.04.x系统上,它也位于Expect-dev软件包中。
Alexandre Mazel 2015年

462

这只猫的另一种使用方法是使用该stdbuf程序,该程序是GNU Coreutils的一部分(FreeBSD也有自己的程序)。

stdbuf -i0 -o0 -e0 command

这将完全关闭针对输入,输出和错误的缓冲。对于某些应用程序,出于性能原因,行缓冲可能更适合:

stdbuf -oL -eL command

请注意,它仅适用于动态链接的应用程序的stdio缓冲(printf()fputs()...),并且仅在该应用程序自身不调整其标准流的缓冲的情况下才起作用,尽管这应涵盖大多数应用程序。


6
需要在软件包内部的Ubuntu中安装“ unbuffer”:
Expect

2
这在默认的raspbian安装中非常有效,可以取消缓冲日志记录。我没有找到sudo stdbuff … command作品stdbuff … sudo command
natevw

20
@qdii stdbuf无法使用tee,因为会tee覆盖设置的默认值stdbuf。请参阅的手册页stdbuf
ceving 2013年

5
@lepe奇怪的是,unbuffer对x11和tcl / tk具有依赖性,这意味着如果将它们安装在没有它们的服务器上,则实际上需要> 80 MB。
jpatokal 2014年

10
@qdii stdbuf使用LD_PRELOAD机制来插入其自己的动态加载的库libstdbuf.so。这意味着它将不适用于以下类型的可执行文件:设置了setuid或文件功能,静态链接,不使用标准libc。在这些情况下,最好将解决方案与unbuffer/ script/ 一起使用socat。另请参见带有setuid / capabilities的stdbuf
pabouk

75

为开启行缓冲输出模式的另一种方法long_running_command是使用在伪终端(pty)script中运行your 的命令long_running_command

script -q /dev/null long_running_command | print_progress      # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress    # Linux

15
+1不错的技巧,因为它script是一个很老的命令,所以它应该在所有类Unix平台上都可用。
亚伦·迪古拉

5
您还需要-q在Linux上:script -q -c 'long_running_command' /dev/null | print_progress
jfs

1
似乎脚本从中读取stdinlong_running_command至少在从交互式终端启动时,这使得无法在后台运行这样的脚本。要解决此问题,我能够从重定向标准输入/dev/null,因为我long_running_command没有使用stdin
haridsv

1
甚至可以在Android上使用。
not2qubit 2014年

3
一个重大缺点:ctrl-z不再起作用(即,我无法暂停脚本)。可以通过以下方法解决此问题:sudo脚本-c / usr / local / bin / ec2-snapshot-all / dev / null | ts,如果您不介意无法与该程序进行交互。
rlpowell

66

对于grepsed并且awk可以强制输出为行缓冲。您可以使用:

grep --line-buffered

强制输出进行行缓冲。默认情况下,当标准输出为端子时,输出为行缓冲,否则为块缓冲。

sed -u

使输出线缓冲。

有关更多信息,请参见此页面:http : //www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html


51

如果在输出未到达终端时libc修改其缓冲/刷新存在问题,则应尝试socat。您可以在几乎任何类型的I / O机制之间创建双向流。其中之一是与伪tty对话的分叉程序。

 socat EXEC:long_running_command,pty,ctty STDIO 

它的作用是

  • 创建一个伪tty
  • 用pty的从属端将std / stdout派生为long_running_command
  • 在pty的主端和第二个地址(此处为STDIO)之间建立双向流

如果此输出与相同long_running_command,则可以继续使用管道。

编辑:哇没有看到unbuffer答案!好吧,无论如何,socat是一个很棒的工具,所以我可能会留下这个答案


1
...而且我不了解socat-看起来有点像netcat,也许更是如此。;)谢谢,并+1。

3
我会用socat -u exec:long_running_command,pty,end-close -在这里
斯特凡Chazelas

20

您可以使用

long_running_command 1>&2 |& print_progress

问题在于libc在标准输出到屏幕时将行缓冲,而在标准输出到文件时将全缓冲。但是对于stderr没有缓冲。

我不认为这是管道缓冲区的问题,而全是关于libc的缓冲区策略的。


你是对的; 我的问题仍然是:如何在不重新编译的情况下影响libc的缓冲策略?
亚伦·迪古拉

@StéphaneChazelasfd1将重定向到stderr
Wang HongQin

@StéphaneChazelas我不明白你的观点。请做一个测试,它可以工作
王宏勤2015年

3
好的,正在发生的事情是,两者zsh|&从csh改编而来)和bash,当您这样做时cmd1 >&2 |& cmd2,fd 1和2都连接到外部stdout。因此,当该外部stdout是终端时,它可以防止缓冲,但这仅是因为输出未通过管道(因此不print_progress打印任何内容)。因此它与long_running_command & print_progress(除了print_progress stdin是没有写程序的管道)相同。您可以与ls -l /proc/self/fd >&2 |& cat进行比较ls -l /proc/self/fd |& cat
斯特凡Chazelas

3
这是因为,字面上的|&缩写2>&1 |。所以cmd1 |& cmd2cmd1 1>&2 2>&1 | cmd2。因此,fd 1和2最终都连接到原始stderr,并且没有任何内容写入管道。(s/outer stdout/outer stderr/g在我之前的评论中)。
斯特凡Chazelas

11

过去是这种情况,也许仍然是这样,当标准输出写入终端时,默认情况下它是行缓冲的-写入换行符时,该行就会写入终端。当标准输出发送到管道时,它将被完全缓冲-因此,仅当标准I / O缓冲区已满时,数据才会发送到管道中的下一个进程。

这就是麻烦的根源。我不确定在不修改写入管道的程序的情况下是否可以做很多修复工作。您可以将setvbuf()函数与_IOLBF标志一起使用,以无条件地stdout进入行缓冲模式。但是我看不到在程序上强制执行此操作的简便方法。或者该程序可以fflush()在适当的时候执行(在输出的每一行之后),但是适用相同的注释。

我想如果用伪终端替换管道,那么标准I / O库将认为输出是终端(因为它是终端的一种),并且会自动进行行缓冲。但是,那是处理事物的复杂方式。


7

我知道这是一个古老的问题,已经有了很多答案,但是如果您想避免缓冲区问题,请尝试以下操作:

stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt

这将实时输出日志,并将它们保存到logs.txt文件中,并且缓冲区将不再影响tail -f命令。


4
这看起来像第二个答案:-/
亚伦·迪古拉

2
stdbuf包含在gnu coreutils中(我在最新版本8.25上进行了验证)。验证了这在嵌入式Linux上的工作原理。
zhaorufei

根据stdbuf的文档, NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for example) then that will override corresponding changes by 'stdbuf'.
shrewmouse

6

我认为问题不在于管道。听起来您长时间运行的进程没有足够频繁地刷新其自己的缓冲区。更改管道的缓冲区大小将是解决问题的办法,但我认为不重建内核是不可能的-您不希望这样做,因为它可能会影响许多其他进程。


18
根本原因是,如果stdout不是tty,则libc切换到4k缓冲。
亚伦·迪古拉

5
那很有趣!因为管道不会引起任何缓冲。它们提供缓冲,但是如果您从管道中读取数据,则可以获得任何可用数据,而不必等待管道中的缓冲。因此,罪魁祸首将是应用程序中的stdio缓冲。


3

chad的回答类似,您可以编写一个小的脚本,如下所示:

# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'

然后使用此scriptee命令代替tee

my-long-running-command | scriptee

las,我似乎无法获得在Linux上完美运行的版本,因此似乎仅限于BSD风格的Unix。

在Linux上,这已经关闭了,但是完成时您不会得到提示(直到按Enter等)。

script -q -c 'cat > /proc/self/fd/1' /dev/null

为什么行得通?“脚本”会关闭缓冲吗?
亚伦·迪古拉

@Aaron Digulla:script模拟终端,是的,我相信它会关闭缓冲。它还回显发送给它的每个字符-这就是为什么cat被送到/dev/null中的例子。就内部运行的程序script而言,它正在与交互式会话对话。我认为这与expect这方面相似,但script很可能是您基本系统的一部分。
jwd

我使用的原因tee是将流的副本发送到文件。文件指定到scriptee哪里?
布鲁诺·布鲁诺斯基

@BrunoBronosky:您是对的,这对这个程序来说是个坏名字。它实际上并没有执行“发球”操作。根据原始问题,它只是禁用输出缓冲。也许应该将其称为“ scriptcat”(尽管它也没有级联...)。无论如何,都可以将cat命令替换为tee myfile.txt,并且应该获得想要的效果。
jwd

2

我找到了这个聪明的解决方案: (echo -e "cmd 1\ncmd 2" && cat) | ./shell_executable

这可以解决问题。cat会读取其他输入(直到EOF为止),然后在echo将其参数放入的输入流后将其传递给管道shell_executable


2
实际上,cat看不到;的输出echo。您只需在子shell中运行两个命令,然后将两者的输出发送到管道中。子外壳程序('cat')中的第二条命令从父级/外部stdin读取,这就是它起作用的原因。
亚伦·迪古拉

0

根据管道缓冲区大小,似乎在内核中进行设置,并会要求你重新编译内核来改变。


7
我相信这是一个不同的缓冲。
塞缪尔·埃德温·沃德,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.