重定向不起作用时保存输出,你使用了 >
,但是有些(或全部)输出仍旧出现在屏幕上。
例如,编译器产生了下列错误消息:
$ gcc bad.c
bad.c: In function `main':
bad.c:3: error: `bad' undeclared (first use in this function)
bad.c:3: error: (Each undeclared identifier is reported only once
bad.c:3: error: for each function it appears in.)
bad.c:3: error: parse error before "c"
$
因为想要获取这些消息,所以你尝试重定向输出:
$ gcc bad.c > save.it
bad.c: In function `main':
bad.c:3: error: `bad' undeclared (first use in this function)
bad.c:3: error: (Each undeclared identifier is reported only once
bad.c:3: error: for each function it appears in.)
bad.c:3: error: parse error before "c"
$
但是,看起来重定向好像没起什么作用。实际上,如果检查重定向文件,你会发现这个文件是空的(长度为 0):
$ ls -l save.it
-rw-r--r-- 1 albing users 0 2005-11-13 15:30 save.it
$ cat save.it
$
解决方案
重定向错误输出,如下所示:
gcc bad.c 2> save.it
现在,save.it 的内容就是你先前看到的错误信息。
讨论
这是怎么回事?Unix 和 Linux 中的每个进程通常一开始都有 3 个已打开的文件描述符:一个用于输入(标准输入 STDIN),一个用于输出(标准输出 STDOUT),一个用于错误消息(标准错误 STDERR)。至于是否遵循这种约定,将错误消息写入标准错误,将正常输出写入标准输出,那真的就得看程序员了,因此,无法确保所有的错误消息都会进入标准错误。但大多数历史较久的实用工具很好地贯彻了这种做法。这就是为什么编译器消息无法使用简单的 > 进行重定向。它重定向的是标准输出,而不是标准错误。
前一节提过,每个文件描述符都由一个数字(从 0 开始)表示。标准输入是 0,标准输出是 1,标准错误是 2。这意味着可以用略显啰唆的写法来重定向标准输出:1>
(而不是简单的 >)跟上文件名,但其实没这个必要,便捷写法 > 就够了。要想重定向标准错误,可以使用 2>。
标准输出和标准错误之间有一处重要不同:前者是缓冲式的(buffered),后者是非缓冲式的(unbuffered)。非缓冲意味着每个字符都是单独写入,不会被收集在一起,然后再批量写入。也就是说,你可以立刻看到错误消息,发生故障时丢失此类消息的可能性较小,但由此带来的就是效率问题。这并不是说标准输出就不可靠,但在发生错误的情况下(例如,某个程序出乎意料地“挂掉”了),缓冲式输出未必能在程序停止执行前将错误消息呈现在屏幕上。这就是标准错误采用非缓冲式的原因:确保能够及时写入消息。相比之下,对于标准输出,只有缓冲区满了(或文件被关闭),才会写入输出。对于频繁用到的输出,这么做效率更高,但要报告错误时,效率并不是最重要的。
如果想要同时保存并查看输出,该怎么办?前面中讨论过的 tee 命令此时就能派上用场了。
gcc bad.c 2>&1 | tee save.it
该命令将标准错误重定向到标准输出,然后通过管道将两者传给 tee 命令。tee 会将其输入写入文件(save.it)和自己的标准输出,因为 tee 并没有重定向标准输出,所以你也可以在屏幕上看到经由管道传来的输入信息。
这是重定向的一种特例,因为重定向的顺序通常很重要。比较以下两个命令:
somecmd >my.file 2>&1
somecmd 2>&1 >my.file
在第一个命令中,标准输出被重定向到文件 my.file,然后标准错误被重定向到和标准输出相同的地方。所有的输出都会出现在 my.file 中。
第二个命令可就不是这样了,其中标准错误被重定向到标准输出(此时标准输出指向的是屏幕),然后,标准输出被重定向到 my.file。因此,只有标准输出消息会出现在文件中,而错误消息仍旧会出现在屏幕上。
但是,这种顺序不适用于管道。你不能将第二个重定向放在管道符号后面,因为管道之后出现的是下一个命令。因此,按照以下方式写入时:
somecmd 2>&1 | othercmd
bash 对此做了特殊处理,它可以识别出标准输出连接到了管道 。因此,当你写出 2>&1
时,bash 会假定你希望将标准错误也连入管道,尽管 2>&1
的正常处理方式并非如此。
这种做法(包括一般的管道语法)带来的另一个结果是,我们无法只将标准错误(而非标准输出)传入其他命令,除非事先交换文件描述符。
bash 4.x 版本中的一种便捷语法可以同时将标准输出和标准错误连入管道。要想将 somecmd 的两种输出流全都传给 othercmd,可以使用|&
:
somecmd |& othercmd
酷客网相关文章:
评论前必须登录!
注册