14

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

要将 stderr 重定向到 stdout 以进行文件输出:

  echo > myfile 2>&1

将 stderr 重定向到管道的标准输出:

  echo 2>&1 | less



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

  echo | less 2>&1 

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

4

3 回答 3

23

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

cmd 2>&1 | less

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

cmd | less 2>&1

在这里,重定向适用于less. Less 的 stdout 和 stderr 可能都开始指向终端,所以2>&1在这种情况下没有效果。

如果要将重定向应用于整个管道、将多个命令分组为管道的一部分或嵌套管道,请使用命令组(或任何其他复合命令):

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

可能是一个典型的例子。最终结果是:cmd1andcmd2的 stderr -> cmd3; cmd2的标准输出 -> cmd3; and的 stderr 和cmd1的stdout -> 终端。cmd3cmd3

如果您使用 Bash 特定的|&管道,事情就会变得奇怪,因为每个管道的标准输出重定向仍然首先发生,但标准错误重定向实际上是最后发生的。例如:

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

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

我建议永远不要|&在 Bash 中使用——它在这里用于演示。

于 2012-07-20T00:05:05.643 回答
8

要添加到 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
于 2012-07-20T00:33:55.887 回答
3

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

i/o 重定向器 > 和 < 默认情况下连接到它们最合理的文件描述符,stout 和 stdin。如果您将 stdout 重新路由到文件(如foo > bar),则在启动进程“foo”时,打开文件“bar”以进行写入并连接到文件描述符编号 1。如果您只希望“bar”中的 stderr,您d 使用foo 2> barwhich 打开文件栏并将其连接到标准错误。

现在是 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”。

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

有了上面,如果你使用foo 2>&1 | bar,由于 stderrfoo被重新路由到 stdout foo, stdout 和 stderr 都foo到达bar.

于 2013-01-21T11:27:18.307 回答