在bash脚本中,使用printf将错误消息写入'stderr'应该使用什么方法?

39
我希望将一个 printf 命令的输出重定向到 stderr,而不是 stdout。我并不想将当前路由的 stderrstdout 重定向。我尝试了一下,发现在 printf 命令后追加 1>&2 可以实现目标。但是,我没有使用过 bash,所以我的主要问题是:有没有更好的方法在 bash 中实现这个目标? “更好”指的是是否有其他更常用、更传统或更惯用的方法?更有经验的 bash 程序员会怎么做?
#!/bin/bash
printf "{%s}   This should go to stderr.\n" "$(date)" 1>&2 
printf "[(%s)] This should go to stdout.\n" "$(date)" 

我还有一个次要的问题。我提出这个问题并不是因为我需要知道答案,而更多地是因为我好奇并想更好地理解正在发生的事情。

看起来上面的代码只能在shell脚本中运行才有效。当我尝试从命令行运行时似乎无法正常工作。

以下是我所说的示例。

irrational@VBx64:~$ printf "{%s} Sent to stderr.\n" "$(date)" 1>&2 2> errors.txt
{Sat Jun  9 14:08:46 EDT 2012} Sent to stderr.
irrational@VBx64:~$ ls -l errors.txt
-rw-rw-r-- 1 irrational irrational 0 Jun  9 14:39 errors.txt

我本来期望上面的printf命令没有输出,因为输出应该被发送到stderr,然后再被写入文件。但实际情况并非如此。怎么回事?

4个回答

48

首先,是的,1>&2 是正确的做法。

其次,你的 1>&2 2>errors.txt 示例之所以不起作用,是因为重定向操作的具体细节问题。

1>&2 的意思是“将文件句柄1指向文件句柄2当前所指向的位置”——也就是原本会输出到标准输出的内容现在会输出到标准错误输出。而 2>errors.txt 的意思是“打开一个指向 errors.txt 的文件句柄,并将文件句柄2指向它”——也就是原本会输出到标准错误输出的内容现在会输出到 errors.txt 文件中。但是文件句柄1并没有受到任何影响,因此仍然会将输出写入到标准输出中。

正确的做法是 2>errors.txt 1>&2,这样既可以将标准输出和标准错误输出都写入到 errors.txt 文件中。因为第一步是“打开 errors.txt 文件并将标准错误输出指向它”,第二步是“将标准输出指向标准错误输出当前所指向的位置”。


1
是的,2>errors.txt 1>&2 确实是我本来想要使用的。回过头来看,我感到有些愚蠢,但这似乎是我不得不一直犯错,直到正确的方法变得“明显”为止的那种傻瓜错误。;-) 非常感谢您的见解。 - irrational John

19
  1. 我认为这很合理。不过,你不需要1,因为它已经被隐含了:

printf "{%s}   This should go to stderr.\n" "$(date)" >&2 
你的问题是一个运算顺序的问题:
$ printf "{%s} Sent to stderr.\n" "$(date)" 2> errors.txt 1>&2
$ ls -l errors.txt 
-rw-r--r--  1 carl  staff  47 Jun  9 11:51 errors.txt
$ cat errors.txt 
{Sat Jun  9 11:51:48 PDT 2012} Sent to stderr.

+1. 更多信息请参见http://www.gnu.org/software/bash/manual/bashref.html#Redirections。 - Oliver Charlesworth
我觉得1>&2比仅仅使用>&2稍微不那么含糊。对吧?(我的意思是,对于查看脚本的人来说,它更清楚地说明了正在发生的事情。显然,bash并不在乎。) - irrational John
当然可以拥有它,只是你不需要它。 - Carl Norum
@irrationalJohn:你问关于惯用语的问题。在使用>&21>&2更加惯用。 - Dennis Williamson

8
典型的惯用语有:
echo foo >&2           # you don't need to specify file descriptor 1
echo foo > /dev/stderr # more explicit, but wordier and less portable

5
问题似乎表明有些混淆。您不想重定向标准输出(stdout),而是希望将printf的输出发送到stderr... 但为了做到这一点,您必须使用重定向。 printf命令总是将正常输出发送到其stdout。这就是printf的作用。为了实现您想要的效果,您必须让shell临时指向您想要的输出位置。请注意,我说的是临时的。当您键入像下面这样的命令时:
$ printf "Hello World!" >&2

在执行printf语句期间,shell会修改stdout句柄。然后,在继续下一个命令之前,shell会恢复stdout的原始值。事实上,如果您想永久重定向stdout,则使用特殊命令。

$ exec >&2

执行完这个命令后,shell将不再记得原始的stdout句柄的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接