这段代码片段来自高级 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 来将ls
stdout 恢复为原始输出(在执行之前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)
或交换stderr
和stdout
:
ls -l 3>&1 1>&2 2>&3 | grep bad
答案3
对于其他因为问题摘要而来到这里的人,而不是理解“高级 Bash 脚本指南”中的代码片段,我发现其他答案很难推理。以下方法适合我的目的,我发现更容易阅读和理解其意图:
$ ls -l 2>&1 1>&- | grep bad
对我来说,这表明我想stdout
通过关闭它来忽略它,然后我想重定向stderr
到,stdout
以便我可以将其传送到下一个命令。