为什么你的命令行程序没有输出

611次阅读  |  发布于3年以前

问题描述

为什么你的程序没有输出?请看下面的命令

tail -f logfile | grep 'foo bar' | awk...

执行上述命令,你会发现你的程序没有产生任何输出,只有当logfile的内容足够多的时候才会产生输出,这是怎么回事呢?

原因

在非交互模式下,大多数的UNIX命令行程序都会缓冲它们的输出,这就意味着程序会缓冲一定数量(通常是4kilobytes)的字符再进行输出,而不是直接输出它的每个字符。在上面这种情况下,grep命令会缓冲它的输出,因此后面的awk命令只会收到一大块的输入。

缓冲区的使用极大地提高了I/O操作的效率,通常情况下其缓冲操作对用户是不可见的,不会影响到用户。在交互式的控制台会话中执行tail -f命令是实时的,但是当命令行程序通过管道连接其它程序的时候,命令行程序可能就无法识别最终的输出是否需要(接近)实时了。幸运的是,在UNIX下有一些技术可以用于控制I/O的缓冲行为。

理解缓冲原理,最重要的是要明确的知道,是写入方(writer)使用的缓冲区,而不是读取方(reader)。

什么是交互模式、非交互模式?

交互式模式就是在终端上执行,shell等待你的输入,并且立即执行你提交的命令。这种模式被称作交互式是因为shell与用户进行交互。这种模式也是大多数用户非常熟悉的:登录、执行一些命令、退出。当你退出后,shell也终止了。

shell也可以运行在另外一种模式:非交互式模式,以shell script(非交互)方式执行。在这种模式 下,shell不与你进行交互,而是读取存放在文件中的命令,并且执行它们。当它读到文件的结尾EOF,shell也就终止了。

参考bash 深入理解:交互式shell和非交互式shell、登录shell和非登录shell的区别

解决方案

排除不需要的命令

回到上面的问题,我们有一个命令行管道程序tail -f logfile | grep 'foo bar' | awk ...。因为tail -f永远都不会缓冲它的输出,因此如果只是运行tail -f logfile的话我们的程序是没有问题的。当标准输出是控制台的时候,grep命令不会使用输出缓冲区,因此在交互模式下,我们运行tail -f logfile | grep 'foo bar'也是没有问题的。现在的问题是如果grep命令的输出是通过管道连接到其它程序(例如上例中的awk命令)的话,它会启用输出缓冲区以提高效率。

下面的命令中去掉了grep命令,使用AWK去实现了筛选操作

tail -f logfile | awk '/foo bar/ ...'

但是这样做依然是不够的,比如我们无法实现对结果进行排序。这种情况下怎么办呢,我们应该总是去寻找最简单的方法,或许你的命令行程序已经支持非缓冲的输出了呢!

grep (e.g. GNU version 2.5.1) --line-buffered
sed (e.g. GNU version 4.0.6) -u,--unbuffered
awk (GNU awk, nawk) use the fflush() function
awk (mawk) -W interactive
tcpdump, tethereal -l

为了让我们的整个管道命令可以(近乎)实时的执行,我们需要告诉管道程序中的每个命令禁用输出缓冲区。管道的最后一个命令可以不需要禁用输出缓冲,因为它的输出是控制台。

在C程序中禁用缓冲区

如果带缓冲的程序是使用C语言开发的,或者你拥有他的源码可以修改它,可以使用下面这个函数禁用缓冲

setvbuf(stdout, 0, _IONBF, 0);

通常情况下只需要在main函数的顶部添加该函数即可。不过如果你的程序关闭并且重新打开了标准输出或者是调用了setvbuf()函数,你可能需要更加仔细一点。

unbuffer

expect 的程序包中包含了一个名为 unbuffer 的程序,它可以有效的欺骗其它程序,让它们以为自己总是在交互模式下执行(交互模式下会禁用缓冲)。

tail -f logfile | unbuffer grep 'foo bar' | awk ...

unbufferunbuffer不是标准的POSIX工具,不过不要担心,你的系统中可能已经安装过它们了。

stdbuf

新版的 GNU coreutils (从7.5开始)新增了一个名为 stdbuf 的程序,使用它也可以用来取消程序的输出缓冲。

tail -f logfile | stdbuf -oL grep 'foo bar' | awk ...

上面的代码中,“-oL” 选项告诉程序使用行缓冲模式,也可以使用“-o0”完全禁止缓冲。

stdbuf也不是标准的POSIX工具,但是你的系统中可能也已经安装了。另外,在Mac系统下可能是没有这个命令的,你需要手动去安装 brew install coreutils,安装之后的该工具的名字叫做gstdbuf

参考

本文大部分内容翻译自What is buffering? Or, why does my command line produce no output: tail -f logfile | grep 'foo bar' | awk ...,内容有删减。

Copyright© 2013-2019

京ICP备2023019179号-2