40

给定一个shell脚本:

#!/bin/sh

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

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

$ > 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

4 回答 4

50
% (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 (fd 2) (stderr),命名为 fd 3(文件描述符 3)。它复制文件描述符,它不复制流tee
  • 2>&1意味着 fd 2 ofsh myscript.sh成为它的 fd 1 (stdout) 的副本。现在,当 myscript 写入它的 stderr(它是 fd 2)时,我们会在 stdout(我们的 fd 1)上接收它。
  • 1>&3表示 fd 1 ofsh myscript.sh成为 fd 3 (stderr) 的副本。现在,当 myscript 写入它的 stdout(它是 fd 1)时,我们会在 stderr(我们的 fd 2)上接收它。
于 2012-11-08T22:59:10.447 回答
17

为了完整起见,根据上面@200_success 的评论,最好使用以下方法移动文件描述符 3 1>&3-

$ (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您可以使用 stdin 和 stderr 交换当前 shell 启动的所有以下命令:

$ (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
于 2016-11-18T12:20:25.060 回答
8

移动文件描述符 ( 1>&3-) 是不可移植的,并非所有 POSIX shell 实现都支持它。它是 ksh93 主义和 bash 主义。(更多信息在这里https://unix.stackexchange.com/questions/65000/practical-use-for-moving-file-descriptors

也可以在执行重定向后关闭 FD 3。

ls 3>&2 2>&1 1>&3 3>&-

将当前工作目录的内容打印出 stderr。

语法3>&-3<&-关闭文件描述符 3。

于 2016-12-12T00:31:44.507 回答
8

bash 黑客 wiki在这类事情上非常有用。有一种方法在这些答案中没有提到,所以我会投入两分钱。

的语义>&N,对于数字 N,意味着重定向到文件描述符 N的目标目标这个词很重要,因为描述符可以稍后更改目标,但是一旦我们复制了该目标,我们就不再关心了。这就是为什么我们声明重定向的顺序是相关的原因。

所以你可以这样做:

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

这意味着:

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

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

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

有趣的是我不能简单地做>&-,而不是>/dev/null。这实际上关闭了标准输出,所以我收到了一个错误(在标准错误的目标上,当然是实际的标准输出:D)

line 3: echo: write error: Bad file descriptor

通过尝试交换重定向,您可以看到该顺序是相关的:

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

这不起作用,因为:

  1. 我们将 stdout 的目标设置为/dev/null
  2. 我们将 stderr 的目标设置为 stdout 的目标,/dev/null又是这样。
于 2017-03-21T08:50:02.120 回答