仅将 stderr 重定向到管道

仅将 stderr 重定向到管道

这段代码片段来自高级 Bash 脚本编写指南

# Redirecting only stderr to a pipe.

exec 3>&1                              # Save current "value" of stdout.
ls -l 2>&1 >&3 3>&- | grep bad 3>&-    # Close fd 3 for 'grep' (but not 'ls').
#              ^^^^   ^^^^
exec 3>&-                              # Now close it for the remainder of the script.

# Thanks, S.C.

注释解释了代码仅针对“grep”关闭 fd 3。但关闭 fd 3 两次。为什么我们需要这样做?像这样为 'grep' 只关闭一次 fd 3 是错误的吗?

ls -l 2>&1 >&3 | grep bad 3>&-    

答案1

既不ls需要grep在其 fd 3 上打开任何内容(他们不使用该 fd),因此最好关闭它(释放不需要的资源)。我们仅使用 fd 3 来将lsstdout 恢复为原始输出(在执行之前ls)。

请记住,在管道中,所有命令在各自的进程中同时运行。重定向分别适用于每一个,因此关闭其中一个的 fd 3 无法关闭另一个的 fd 3。

在这种情况下,关闭或不关闭在实践中不会有太大区别,除非如果不关闭,您可能会更快达到文件描述符数量的限制。

在其他情况下(例如命令本身启动其他进程),这可能会占用资源并导致问题。例如,如果 stdout 是一个管道,则后台进程可能最终会继承该 fd,从而阻止该管道的任何读取器在该进程返回之前看到 EOF。

更好的写法是:

{ ls -l 2>&1 >&3 3>&- | grep bad 3>&-; } 3>&1

这样,fd 3 仅在该命令组的持续时间内临时重定向(并在之后恢复或关闭)。

看到不同:

$ { ls -l /proc/self/fd 2>&1 >&3 3>&- | grep bad 3>&-; } 3>&1
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:29 0 -> /dev/pts/4
lrwx------ 1 stephane stephane 64 Apr  2 09:29 1 -> /dev/pts/4
l-wx------ 1 stephane stephane 64 Apr  2 09:29 2 -> pipe:[575886]
lr-x------ 1 stephane stephane 64 Apr  2 09:29 3 -> /proc/20918/fd/
$ { ls -l /proc/self/fd 2>&1 >&3  | grep bad 3>&-; } 3>&1
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:29 0 -> /dev/pts/4
lrwx------ 1 stephane stephane 64 Apr  2 09:29 1 -> /dev/pts/4
l-wx------ 1 stephane stephane 64 Apr  2 09:29 2 -> pipe:[575900]
lrwx------ 1 stephane stephane 64 Apr  2 09:29 3 -> /dev/pts/4
lr-x------ 1 stephane stephane 64 Apr  2 09:29 4 -> /proc/20926/fd/

在第二次调用中,ls它的 fd 3 也无缘无故地向终端打开(在我运行管道时在标准输出上打开)。

请注意,在 ksh93 中,使用exec,您不需要关闭这些 fd,因为除了 0、1、2 之外的 fd 在执行命令时会自动关闭。

$ ksh93 -c 'exec 3>&1; ls -l /dev/fd/'
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:34 0 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 1 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 2 -> /dev/pts/16
lr-x------ 1 stephane stephane 64 Apr  2 09:34 3 -> /proc/21105/fd
$ ksh93 -c 'exec 3>&1; ls -l /dev/fd/ 3>&3'
total 0
lrwx------ 1 stephane stephane 64 Apr  2 09:34 0 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 1 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 2 -> /dev/pts/16
lrwx------ 1 stephane stephane 64 Apr  2 09:34 3 -> /dev/pts/16
lr-x------ 1 stephane stephane 64 Apr  2 09:34 4 -> /proc/21108/fd

我不知道为什么它说(但不是“ls”)上面,听起来像是一个错误(可能是我的错误;-)。

答案2

关闭fd3两次,因为您在脚本中分叉了两个子 shell,每个子 shell 都会继承并复制父级的文件描述符。每个子 shell 中的关闭fd3不会影响其他子 shell(也包括父 shell)。

所以注释行非常不明确,容易产生误导。

如果您只想重定向stder到管道,可以使用process substitution

ls -l 2> >(grep bad)

或交换stderrstdout

ls -l 3>&1 1>&2 2>&3 | grep bad

答案3

对于其他因为问题摘要而来到这里的人,而不是理解“高级 Bash 脚本指南”中的代码片段,我发现其他答案很难推理。以下方法适合我的目的,我发现更容易阅读和理解其意图:

$ ls -l 2>&1 1>&- | grep bad

对我来说,这表明我想stdout通过关闭它来忽略它,然后我想重定向stderr到,stdout以便我可以将其传送到下一个命令。

相关内容