IO重定向 - 交换标准输出和标准错误输出

45

给定一个 shell 脚本:

#!/bin/sh

echo "I'm stdout";
echo "I'm stderr" >&2;

有没有一种方法调用该脚本,使得只有stderr会打印输出,当命令的最后一部分是2>/dev/null时,即

$ > sh myscript.sh SOME_OPTIONS_HERE 2>/dev/null
I'm stderr

或者,另外一种选择:

$ > sh myscript.sh SOME_OPTIONS_HERE >/dev/null
I'm stdout

这是一组讲义幻灯片末尾的问题,但我几乎可以确定经过近一天的努力工作后,这一定是某种打字错误。转轴操作不起作用。2>&-不起作用。我已经没有更多的想法了!

4个回答

58
% (sh myscript.sh 3>&2 2>&1 1>&3) 2>/dev/null
I'm stderr
% (sh myscript.sh 3>&2 2>&1 1>&3) >/dev/null 
I'm stdout

对于 3>&2 2>&1 1>&3 的解释:

  • 3>&2 表示将文件描述符 2(标准错误输出)的一个副本,命名为文件描述符 3。这个操作会复制文件描述符,而不是像 tee 一样复制流。
  • 2>&1 表示将当前 shell 中的文件描述符 2(标准错误输出)的一个副本,命名为文件描述符 1(标准输出)。现在,当脚本写入 stderr(它的文件描述符 2)时,我们会在 stdout(我们的文件描述符 1)中接收到它。
  • 1>&3 表示将当前 shell 中的文件描述符 1(标准输出)的一个副本,命名为文件描述符 3(标准错误输出)。现在,当脚本写入 stdout(它的文件描述符 1)时,我们会在 stderr(我们的文件描述符 2)中接收到它。

太好了!谢谢。我可以问一下括号的意义是什么吗? - Richard
2
@Richard 只是为了避免关于双重重定向的混淆而使用 shell。无法在同一命令中两次重定向 FD。 - unbeli
14
使用1>&3-移动文件描述符比仅复制文件描述符要好一些,因为这样您不会结束时有一个打开的FD 3。 - 200_success
4
虽然已经以各种方式表达,但为完整起见,我想强调一下:在交换完成后,我们可能希望关闭临时文件描述符,因为我们不再需要它,因此完整的示例将是:myscript.sh 3>&2 2>&1 1>&3 3>&- …。在bash中,可以动态分配文件描述符:myscript.sh {FD}>&2 2>&1 1>&$FD {FD}>&-) …,其中FD是一个变量名。 - Stanislav German-Evtushenko
我不确定“在同一命令中不能重定向FD两次”是()的恰当解释。我的解释是:()内的内容被视为一组,保护它不参与bash按照倒序(在我看来)排序重定向序列的方式。第一个命令的替代写法:sh myscript.sh 2>/dev/null 3>&2 2>&1 1>&3或者(((sh myscript.sh 1>&3) 2>&1) 3>&2) 2>/dev/null - Don Hatch

18

为了完整起见,基于上面@200_success的评论,更好的方法可能是使用1>&3-移动文件描述符3:move

$ (sh myscript.sh 3>&2 2>&1 1>&3-) 2>/dev/null
I'm stderr
$ (sh myscript.sh 3>&2 2>&1 1>&3-) >/dev/null 
I'm stdout

使用exec而非以每个进程为基础交换文件描述符,您可以在当前shell启动的所有后续命令中交换stdin和stderr:

$ (exec 3>&2 2>&1 1>&3- ; sh myscript.sh ; sh myscript.sh ) 2>/dev/null
I'm stderr
I'm stderr
$ (exec 3>&2 2>&1 1>&3- ; sh myscript.sh ; sh myscript.sh ) >/dev/null 
I'm stdout
I'm stdout

10

9
Bash Hackers Wiki在这种情况下非常有用。这里有一种未在回答中提到的方法,因此我也要发表我的看法。
对于数字N,>&N的语义意思是重定向到文件描述符N的目标 。单词目标很重要,因为描述符可以稍后更改目标,但一旦我们复制了该目标,我们就不关心它。这就是为什么声明重定向的顺序很重要的原因。
所以你可以这样做:
./myscript.sh 2>&1 >/dev/null

这意味着:

  1. 将 stderr 重定向到 stdout 的目标,也就是 stdout 的输出流。现在 stderr 复制了 stdout 的目标。

  2. 将 stdout 更改为 /dev/null。这不会影响 stderr,因为它在我们更改之前“复制”了目标。

不需要第三个文件描述符。

有趣的是,我无法简单地执行 >&- 而不是 >/dev/null。这实际上关闭了 stdout,所以我会得到一个错误(当然是在 stderr 的目标上,也就是实际的 stdout :D)

line 3: echo: write error: Bad file descriptor

您可以尝试交换重定向来查看顺序的影响,这表明顺序在此处具有重要性:

./myscript.sh >/dev/null 2>&1

这样做是行不通的,因为:
  1. 我们将 stdout 的目标设置为 /dev/null
  2. 我们将 stderr 的目标设置为 stdout 的目标,即再次是 /dev/null

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