我有一个脚本,它调用两个命令:
long_running_command | print_progress
该long_running_command
打印进度,但我不开心吧。我print_progress
用来使其更美观(即,我将进度打印在一行中)。
问题:将管道连接到stdout还会激活4K缓冲区,到漂亮的打印程序不会得到任何东西……没有任何东西……没有任何东西 ……:)
如何禁用4K缓冲区long_running_command
(不,我没有源)?
我有一个脚本,它调用两个命令:
long_running_command | print_progress
该long_running_command
打印进度,但我不开心吧。我print_progress
用来使其更美观(即,我将进度打印在一行中)。
问题:将管道连接到stdout还会激活4K缓冲区,到漂亮的打印程序不会得到任何东西……没有任何东西……没有任何东西 ……:)
如何禁用4K缓冲区long_running_command
(不,我没有源)?
Answers:
您可以使用unbuffer
命令(expect
软件包附带的命令),例如
unbuffer long_running_command | print_progress
unbuffer
连接到long_running_command
经由一伪终端(PTY),这使得该系统把它作为一个互动的过程,因此不使用在管道即延迟的可能原因的4 KIB缓冲。
对于更长的管道,您可能必须取消缓冲每个命令(最后一个命令除外),例如
unbuffer x | unbuffer -p y | z
expect_unbuffer
,并且在expect-dev
软件包中,而不是在expect
软件包中
expect-dev
提供unbuffer
和expect_unbuffer
(前者是后者的符号链接)。自expect 5.44.1.14-1
2009年以来,这些链接可用。
这只猫的另一种使用方法是使用该stdbuf
程序,该程序是GNU Coreutils的一部分(FreeBSD也有自己的程序)。
stdbuf -i0 -o0 -e0 command
这将完全关闭针对输入,输出和错误的缓冲。对于某些应用程序,出于性能原因,行缓冲可能更适合:
stdbuf -oL -eL command
请注意,它仅适用于动态链接的应用程序的stdio
缓冲(printf()
,fputs()
...),并且仅在该应用程序自身不调整其标准流的缓冲的情况下才起作用,尽管这应涵盖大多数应用程序。
sudo stdbuff … command
作品stdbuff … sudo command
。
stdbuf
无法使用tee
,因为会tee
覆盖设置的默认值stdbuf
。请参阅的手册页stdbuf
。
stdbuf
使用LD_PRELOAD
机制来插入其自己的动态加载的库libstdbuf.so
。这意味着它将不适用于以下类型的可执行文件:设置了setuid或文件功能,静态链接,不使用标准libc。在这些情况下,最好将解决方案与unbuffer
/ script
/ 一起使用socat
。另请参见带有setuid / capabilities的stdbuf。
为开启行缓冲输出模式的另一种方法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
script
是一个很老的命令,所以它应该在所有类Unix平台上都可用。
-q
在Linux上:script -q -c 'long_running_command' /dev/null | print_progress
stdin
,long_running_command
至少在从交互式终端启动时,这使得无法在后台运行这样的脚本。要解决此问题,我能够从重定向标准输入/dev/null
,因为我long_running_command
没有使用stdin
。
对于grep
,sed
并且awk
可以强制输出为行缓冲。您可以使用:
grep --line-buffered
强制输出进行行缓冲。默认情况下,当标准输出为端子时,输出为行缓冲,否则为块缓冲。
sed -u
使输出线缓冲。
有关更多信息,请参见此页面:http : //www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html
如果在输出未到达终端时libc修改其缓冲/刷新存在问题,则应尝试socat。您可以在几乎任何类型的I / O机制之间创建双向流。其中之一是与伪tty对话的分叉程序。
socat EXEC:long_running_command,pty,ctty STDIO
它的作用是
如果此输出与相同long_running_command
,则可以继续使用管道。
编辑:哇没有看到unbuffer答案!好吧,无论如何,socat是一个很棒的工具,所以我可能会留下这个答案
socat -u exec:long_running_command,pty,end-close -
在这里
您可以使用
long_running_command 1>&2 |& print_progress
问题在于libc在标准输出到屏幕时将行缓冲,而在标准输出到文件时将全缓冲。但是对于stderr没有缓冲。
我不认为这是管道缓冲区的问题,而全是关于libc的缓冲区策略的。
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
。
|&
缩写2>&1 |
。所以cmd1 |& cmd2
是cmd1 1>&2 2>&1 | cmd2
。因此,fd 1和2最终都连接到原始stderr,并且没有任何内容写入管道。(s/outer stdout/outer stderr/g
在我之前的评论中)。
过去是这种情况,也许仍然是这样,当标准输出写入终端时,默认情况下它是行缓冲的-写入换行符时,该行就会写入终端。当标准输出发送到管道时,它将被完全缓冲-因此,仅当标准I / O缓冲区已满时,数据才会发送到管道中的下一个进程。
这就是麻烦的根源。我不确定在不修改写入管道的程序的情况下是否可以做很多修复工作。您可以将setvbuf()
函数与_IOLBF
标志一起使用,以无条件地stdout
进入行缓冲模式。但是我看不到在程序上强制执行此操作的简便方法。或者该程序可以fflush()
在适当的时候执行(在输出的每一行之后),但是适用相同的注释。
我想如果用伪终端替换管道,那么标准I / O库将认为输出是终端(因为它是终端的一种),并且会自动进行行缓冲。但是,那是处理事物的复杂方式。
我知道这是一个古老的问题,已经有了很多答案,但是如果您想避免缓冲区问题,请尝试以下操作:
stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt
这将实时输出日志,并将它们保存到logs.txt
文件中,并且缓冲区将不再影响tail -f
命令。
NOTE: If COMMAND adjusts the buffering of its standard streams ('tee' does for example) then that will override corresponding changes by 'stdbuf'.
我认为问题不在于管道。听起来您长时间运行的进程没有足够频繁地刷新其自己的缓冲区。更改管道的缓冲区大小将是解决问题的办法,但我认为不重建内核是不可能的-您不希望这样做,因为它可能会影响许多其他进程。
与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
script
模拟终端,是的,我相信它会关闭缓冲。它还回显发送给它的每个字符-这就是为什么cat
被送到/dev/null
中的例子。就内部运行的程序script
而言,它正在与交互式会话对话。我认为这与expect
这方面相似,但script
很可能是您基本系统的一部分。
tee
是将流的副本发送到文件。文件指定到scriptee
哪里?
cat
命令替换为tee myfile.txt
,并且应该获得想要的效果。