为什么你的命令行程序没有输出
问题描述
为什么你的程序没有输出?请看下面的命令
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也就终止了。
解决方案
排除不需要的命令
回到上面的问题,我们有一个命令行管道程序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 ...
unbuffer
和unbuffer
不是标准的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 ...,内容有删减。