2012-07-19 31 views
10

将stderr与stdout结合使用时,为什么2>&1需要在|(管道)之前,但在> myfile(重定向到文件)之后?为什么2>&1需要在|之前出现(管道),但后“> myfile”(重定向到文件)?

重定向错误输出到标准输出文件输出:

echo > myfile 2>&1 

重定向错误输出到标准输出的管道:

echo 2>&1 | less 



我的假设是,我可以这样做:

echo | less 2>&1 

它很害怕d工作,但事实并非如此。为什么不?

回答

16

A 管道命令的限制列表。您指定的任何重定向适用于组成命令(简单或复合),但不适用于整个管道。每个管道将一个命令的stdout链接到下一个stdin,方法是隐式地将重定向应用于每个子shell ,然后对与命令相关联的任何重定向进行评估。

cmd 2>&1 | less 

第一子外壳的第一标准输出被重定向到从中less正在读取管。接下来,将2>&1重定向应用于第一个命令。将标准错误重定向到标准输出是可行的,因为标准输出已经指向管道。

cmd | less 2>&1 

这里,重定向适用于less。较少的stdout和stderr都可能开始指向终端,因此在这种情况下2>&1不起作用。

如果要重定向到适用于整个管道,将多个命令作为管道的一部分,或者嵌套管道,然后使用一个命令组(或任何其它化合物命令):

{ { cmd1 >&3; cmd2; } 2>&1 | cmd3; } 3>&2 

可能是一个典型的例子。最终结果是:cmd1cmd2的stderr - >cmd3; cmd2的标准输出 - >cmd3;和cmd1cmd3的stderr,和cmd3的标准输出 - >终端。

如果使用特定于Bash的|&管道,事情会变得很陌生,因为每个管道的stdout重定向仍然首先发生,但stderr重定向实际上是最后一次。例如:

f() { echo out; echo err >&2; }; f >/dev/null |& cat 

现在,违反直觉,所有的输出都是隐藏的。 f的第一个stdout进入管道,f的下一个stdout被重定向到/dev/null,最后,stderr被重定向到stdout(仍然为/dev/null)。

我推荐在Bash中从不使用|& - 这里用于演示。

+3

+1好解释 – jordanm 2012-07-20 00:19:17

+2

+1。我唯一要添加的是管道是一个命令分隔符,就像分号一样。 – 2012-07-20 02:19:55

+1

+1哇,优秀的答案@ormaaj!正是我在找什么 - 谢谢! – 2012-07-20 14:23:37

6

为了增加ormaaj的回答是:

你需要以适当的顺序来指定重定向操作符的原因是由左到右,他们正在评估。考虑这些命令列表:

# print "hello" on stdout and "world" on stderr 
{ echo hello; echo world >&2; } 

# Redirect stdout to the file "out" 
# Then redirect stderr to the file "err" 
{ echo hello; echo world >&2; } > out 2> err 

# Redirect stdout to the file "out" 
# Then redirect stderr to the (already redirected) stdout 
# Result: all output is stored in "out" 
{ echo hello; echo world >&2; } > out 2>&1 

# Redirect stderr to the current stdout 
# Then redirect stdout to the file "out" 
# Result: "world" is displayed, and "hello" is stored in "out" 
{ echo hello; echo world >&2; } 2>&1 > out 
2

我的答案是通过理解文件描述符。每个进程都有一堆文件描述符:打开文件的条目。默认情况下,编号0用于标准输入,编号1用于标准输出,编号2用于标准错误。

默认情况下,I/O转向器>和<连接到它们最合理的文件描述符stout和stdin。如果将stdout重新路由到文件(如foo > bar),则在启动进程'foo'时,会打开文件'bar'进行写入并挂接文件描述符编号1.如果只希望在'bar'中使用stderr, '使用foo 2> bar打开文件栏并将其挂接到stderr。

现在I/O转向器'2> & 1'。我通常读为'将文件描述符2放置为与文件描述符1相同。从左到右阅读命令行时,您可以执行下一个:foo 1>bar 2>&1 1>/dev/tty。由此,文件描述符1被设置为文件'bar',文件描述符2被设置为与1(因此'bar')相同,并且之后,文件描述符1被设置为/ dev/tty。运行foo将其输出发送到/ dev/tty,并将其stderr发送到文件“bar”。

现在流水线进来了:这不会改变文件描述符,但是它会在进程之间连接它们:下一个进程的stdin标准输出。斯德尔传递。因此,如果您希望管道仅在stderr上工作,则使用foo 2| bar,它将foo的stderr连接到bar的stdin。 (我不知道用的foo标准输出会发生什么。)

通过以上,如果使用foo 2>&1 | bar,因为foo标准错误重新路由到的foo标准输出,标准输出和标准错误foo在到达标准号为bar

相关问题