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

long_running_command | print_progress


long_running_command打印进度,但我对此不满意。我正在使用print_progress使其更好(即,我在一行中打印进度)。

问题:将管道连接到stdout还会激活4K缓冲区,因此好的打印程序什么...什么...什么...什么... ...)

如何禁用4Klong_running_command缓冲区(不,我没有源代码)?

评论

因此,当您在没有管道的情况下运行long_running_command时,您可以看到进度更新正确,但是在管道被缓冲时,它们会被缓冲吗?

是的,这正是发生的情况。

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

serverfault.com/a/589614/67097

实际上,不是在等待足够数据时导致延迟的管道是stdio。管道确实具有容量,但是一旦有数据写入管道,就可以立即在另一端读取它。

#1 楼

您可以使用unbuffer命令(属于expect软件包的一部分),例如,

unbuffer long_running_command | print_progress


unbuffer通过伪端子(pty)连接到long_running_command,这使得系统将其视为交互过程,因此不要在流水线中使用可能导致延迟的4-kiB缓冲。

对于更长的流水线,可能必须取消缓冲每个命令(最终命令除外)。一个),例如

unbuffer x | unbuffer -p y | z


评论


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

–谢杜多
09年6月17日在7:58

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

–克里斯·康威(Chris Conway)
09年10月6日在20:18

注意:在debian系统上,这称为Expect_unbuffer,它位于Expect-dev软件包中,而不是Expect软件包中

–bdonlan
2011年1月24日,11:14

@bdonlan:至少在Ubuntu(基于Debian的)上,Expect-dev同时提供了unbuffer和Expect_unbuffer(前者是后者的符号链接)。由于预期5.44.1.14-1(2009),因此可以使用这些链接。

– jfs
2013年4月11日13:00在

unbuffer现在在debian上的主Expect软件包中(它仍然是Expect_unbuffer的符号链接,这也在主Expect软件包中)

–cas
2015年11月4日23:50



#2 楼

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

stdbuf -i0 -o0 -e0 command


这将关闭缓冲完全用于输入,输出和错误。对于某些应用程序,出于性能原因,行缓冲可能更合适:应用程序,并且仅在该应用程序不能自行调整其标准流的缓冲的情况下,尽管这应涵盖大多数应用程序。

评论


需要在软件包内部的Ubuntu中安装“ unbuffer”:Expect-dev大小为2MB ...

–lepe
2013年6月27日6:21



这在默认的raspbian安装中非常有效,可以取消日志记录。我发现sudo stdbuff…命令有效,尽管stdbuff…sudo命令没有。

–natevw
13年10月10日在6:05

@qdii stdbuf不适用于tee,因为tee会覆盖stdbuf设置的默认值。请参见stdbuf的手册页。

–天花板
2014年6月30日在11:51

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

–lambshaanxy
2014年8月28日在12:27

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

– pabouk
15-10-12在9:20



#3 楼

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

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


评论


+1不错的技巧,因为脚本是一个很老的命令,所以它应该在所有类Unix平台上都可用。

–亚伦·迪古拉(Aaron Digulla)
13年1月20日在13:01

在Linux上,您还需要-q:脚本-q -c'long_running_command'/ dev / null | print_progress

– jfs
13年4月11日在12:51

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

–haridsv
13年11月15日在12:44



甚至适用于Android。

–not2qubit
2014年7月2日在23:36

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

–rlpowell
15年7月24日在0:03

#4 楼

对于grepsedawk,您可以强制输出进行行缓冲。您可以使用:

grep --line-buffered


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

sed -u


使输出行缓冲。

请参阅此页面以获取更多信息:
http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html

评论


值得注意的是,python还支持-u参数以禁用缓冲。

–大卫·帕克斯(David Parks)
19/12/19在21:52

像这样使用grep(etc。)是行不通的。到您执行long_running_command时,为时已晚。它将在到达grep之前进行缓冲。

–tgm1024--莫妮卡遭到虐待
1月24日15:26

这仍然被缓冲。如果要查看生产线进度怎么办...

–迈克尔
2月3日18:48

应该与grep --line-buffered模式*很多*很多*文件* |一起使用吗?头?看起来grep在将输出行送入head之前先处理所有文件

– golimar
2月28日上午11:27

#5 楼

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

它的作用是

创建伪tty
fork long_running_command,而从属端则是伪tty。将pty作为stdin / stdout
在pty的主端与第二个地址(此处为STDIO)之间建立双向流。

如果此输出与long_running_command相同,则您可以继续使用管道。
编辑:哇
没看到unbuffer答案!好吧,无论如何,socat是一个很棒的工具,所以我可能只留下这个答案

评论


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

–谢杜多
09年6月20日在9:32

我会用socat -u exec:long_running_command,pty,end-close-在这里

–StéphaneChazelas
15年8月7日,13:10

#6 楼

您可以使用

long_running_command 1>&2 |& print_progress


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

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

评论


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

–亚伦·迪古拉(Aaron Digulla)
2014年4月4日在8:55

@StéphaneChazelasfd1将重定向到stderr

–王宏勤
15年8月7日在9:26

@StéphaneChazelas我不明白你的观点。请做一个测试,它的工作原理

–王宏勤
15年8月7日在9:53

好的,正在发生的是zsh(其中|&来自csh的改编)和bash,当执行cmd1>&2 |&cmd2时,fd 1和2都连接到外部stdout。因此,当外部标准输出是终端时,它可以防止缓冲,但这仅是因为输出没有通过管道(所以print_progress不输出任何内容)。因此,它与long_running_command和print_progress相同(除了print_progress stdin是没有写程序的管道)。与ls -l / proc / self / fd |&cat比较,您可以使用ls -l / proc / self / fd>&2 |&cat进行验证。

–StéphaneChazelas
2015年8月7日在10:44



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

–StéphaneChazelas
2015年8月7日在10:48



#7 楼

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

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

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

评论


实际上,这是一种处理问题的简便方法,正如问题所表明的那样,更改程序代码不是一种选择。 unix.stackexchange.com/a/215071/5132

– JdeBP
1月17日19:11

#8 楼

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

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


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

评论


这看起来像第二个答案:-/

–亚伦·迪古拉(Aaron Digulla)
15年8月11日在11:17

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

–赵如飞
16年7月20日在10:18

从stdbuf的文档中,请注意:如果COMMAND调整其标准流的缓冲(例如'tee'进行缓冲),则它将覆盖'stdbuf'的相应更改。

–滑鼠
19-09-17在20:58

#9 楼

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

评论


根本原因是,如果stdout不是tty,则libc切换到4k缓冲。

–亚伦·迪古拉(Aaron Digulla)
09年6月16日在11:50

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

– Shodanex
09年6月16日在13:58

#10 楼

与chad的回答类似,您可以编写一个小脚本,如下所示: br />
# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'


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

在Linux上,这很接近,但是完成时您不会得到提示(直到按Enter等)...

my-long-running-command | scriptee


评论


为什么行得通? “脚本”是否关闭缓冲?

–亚伦·迪古拉(Aaron Digulla)
16年6月6日在10:09

@Aaron Digulla:脚本模拟了终端,是的,我相信它会关闭缓冲。它还回显发送给它的每个字符-这就是为什么在示例中cat被发送到/ dev / null的原因。就在脚本中运行的程序而言,它是在进行交互式会话。我相信在这方面期望与之相似,但脚本可能是您基本系统的一部分。

– jwd
16-12-7在18:54



我使用tee的原因是将流的副本发送到文件。该文件在哪里指定给scriptee?

–布鲁诺·布鲁诺斯基(Bruno Bronosky)
19年7月31日在19:26

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

– jwd
19年7月31日在21:51

#11 楼

根据此处的这篇文章,您可以尝试将管道ulimit减少到一个512字节块。它当然不会关闭缓冲,但是512字节小于4K:3

#12 楼

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

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

评论


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

–亚伦·迪古拉(Aaron Digulla)
16年9月9日,11:19

#13 楼

jq具有--unbuffered标志:

在打印每个JSON对象后刷新输出(如果将慢速数据源输送到jq并将jq的输出输送到其他位置,则很有用)。


评论


许多工具都有这样的选项,但我的问题实际上是关于如何针对任何工具修复该问题。我想我必须向GLIBC提出功能请求。

–亚伦·迪古拉(Aaron Digulla)
6月19日9:12

#14 楼

据此,管道缓冲区的大小似乎是在内核中设置的,需要您重新编译内核才能更改。

评论


我相信这是一个不同的缓冲。

–塞缪尔·埃德温·沃德(Samuel Edwin Ward)
13年1月8日在21:58