为什么2>& 1需要在|之前(管道)但是在“> myfile”之后(重定向到文件)?

时间:2012-07-19 23:41:03

标签: bash shell pipe stdout pipeline

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

将stderr重定向到stdout以获取文件输出:

  echo > myfile 2>&1

将stderr重定向到管道的stdout:

  echo 2>&1 | less



我的假设是我可以做到:

  echo | less 2>&1 

它会起作用,但它没有。为什么不呢?

3 个答案:

答案 0 :(得分:20)

管道命令的|分隔列表。您指定的任何重定向都适用于组成命令(简单或复合),但不适用于整个管道。每个管道通过在评估与命令关联的任何重定向之前隐式地将重定向应用于每个子shell ,将一个命令的stdout链接到下一个stdin的stdin。

cmd 2>&1 | less

第一个子shell的第一个stdout被重定向到less正在读取的管道。接下来,2>&1重定向将应用于第一个命令。将stderr重定向到stdout是有效的,因为stdout已经指向管道。

cmd | less 2>&1

此处,重定向适用于less。 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中使用|& - 它在这里用于演示。

答案 1 :(得分:7)

添加到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 :(得分:3)

我的回答是了解文件描述符。每个进程都有一堆文件描述符:打开的文件的条目。默认情况下,数字0表示stdin,数字1表示stdout,数字2表示stderr。

i / o重定向器>和<默认情况下,连接到最合理的文件描述符stout和stdin。如果将stdout重新路由到文件(与foo > bar一样),则在启动进程'foo'时,打开文件'bar'进行写入并挂钩文件描述符编号1.如果只想要stderr'' bar',你使用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。 runnning foo将其输出发送到/ dev / tty,并将其stderr发送到文件'bar'。

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

如果使用foo 2>&1 | bar,由于foo的stderr被重新路由到foo的stdout,foo的stdout和stderr都会到达st bar