I. 问题:我们真的可以重定向 shell 标准错误吗?

I. 问题:我们真的可以重定向 shell 标准错误吗?

几周前,我看到一个奇怪的答案关于这个问题“(如何)在后台静默启动任务?“。这个解决方案似乎不正确(比照我的回答)尽管 shell 似乎在后台默默地启动任务。

I. 问题:我们真的可以重定向 shell 标准错误吗?

所提出的解决方案没有任何解释,分析也没有提供可靠的答案。

下面,您可以看到代码片段。

# Run the command given by "$@" in the background
silent_background() {
   if [[ -n $BASH_VERSION ]]; then
      { 2>&3 "$@"& } 3>&2 2>/dev/null
   fi
}

有问题的命令是{ 2>&3 "$@"& } 3>&2 2>/dev/null.

一)分析

命令组 ( )为一个命令 ( ){ ... }指定两个重定向 (3>&2和) 。该命令是一个具有一个重定向 ( ) 的异步列表 ( )。2>/dev/null# Run the command given by "$@" in the backgroundcmd&2>&3

POSIX规范

每个重定向应适用于复合命令中未显式覆盖该重定向的所有命令。

标准错误重定向2>/dev/null被与异步列表关联的重定向覆盖2>&3

具体案例

在第一种情况下,标准错误被重定向到/dev/null,而在第二种情况下,标准错误被重定向到命令仍然连接到终端。

prompt% { grep warning system.log& } 2>/dev/null
prompt% { 2>&3 grep warning system.log& } 3>&2 2>/dev/null
grep: system.log: No such file or directory

下面,我们可以看到类似的案例。在第一种情况下,标准输出命令仍然连接到终端。在第二种情况下,标准输出重定向命令被修改:>echo.txt被 覆盖>print.txt

prompt% { >&3 echo some data...& } 3>&1 >echo.txt
[1] 3842
some data...
prompt% file echo.txt
echo.txt: empty
prompt% { >&3 echo some data...& } 3>print.txt >echo.txt
[1] 2765
prompt% file echo.txt
echo.txt: empty
prompt% cat print.txt
some data...

ii) 观察结果

如前所述,该命令似乎在后台默默启动。更准确地说,[1] 3842不显示有关后台作业的通知,例如。

前面的分析表明重定向是多余的,因为它们可能会被取消。

{ redir_3 cmd& } redir_1 redir_2相当于cmd&.

iii) 解释

在我看来,上述构造由于副作用而隐藏了通知。

你能解释一下这是怎么发生的吗?

二.假设

Ilkkachu 的回答和评论允许一些进展。请注意,每个进程都有自己的文件描述符。

  • shell 消息可以在 shell 标准错误上发送。

另一个上下文:在子 shell 中执行的异步列表。该命令g不存在。

prompt% ( g& )
g: command not found

异步命令、用括号分组的命令...在与 shell 环境重复的子 shell 环境中执行...

子 shell 从其父 shell 继承其标准错误流的值。

因此,在前一种情况下,它们的标准错误流应该指向终端。但是,作业通知没有显示,而 shell 错误消息可能显示在 shell 标准错误上。

答案1

您错过了最重要的一点,shell 重定向是按从左到右的顺序应用的。

在:

{ 2>&3 "$@"& } 3>&2 2>/dev/null

整个组命令的运行方式为:

  • 文件描述符 3 => 标准错误,即终端此时。
  • 文件描述符 2(标准错误)=> /dev/null

因此,当分组内的命令运行时:

  • 标准错误 => 文件描述符 3,指向终端。

因此,如果"$@"&将任何内容打印到其标准错误,则输出将打印到终端。


对于您的具体情况:

{ grep warning system.log& } 2>/dev/null

{ grep warning system.log& }运行时带有指向的标准错误/dev/nullgrep不会覆盖任何重定向,因此其标准错误与 相同{...},并且被重定向到/dev/null,您没有输出到终端。

在:

{ 2>&3 grep warning system.log& } 3>&2 2>/dev/null

grep的标准错误被重定向到文件描述符 3,它指向终端,如上所述,因此您可以将输出输出到终端。

答案2

在交互式 Bash 中,在后台foo &运行,并将其作业 id 和进程 id 打印到 shell 的标准错误中。foo

foo 2>/dev/null &foo在后台运行,其 stderr 重定向到/dev/null,但仍将作业 id 和进程 id 打印到贝壳的标准错误(不是重定向的 stderr foo)。

{ foo & } 2>/dev/null首先将 stderr 重定向到,然后应用该重定向来处理 的/dev/null内部。{}然后foo在后台启动,并将作业id和进程id打印到现在重定向shell 的 stderr(即/dev/null)。

Bash 的手册暗示组外的重定向适用于整个组:

当命令被分组时,重定向可以应用于整个命令列表。

我们可以验证应用的重定向组是否也重定向了作业 ID 输出:

$ { 睡眠 9 & } 2> 温度
[这里没有输出]
$ 猫体温
[1]8490

然而,当命令最终退出时,该通知确实会出现在终端上:(或者更确切地说,显示到 shell 的当前 stderr,无论是什么。)

$ kill %1
[1]+  Terminated              sleep 99

在 中{ ... } 3>&2 2>/dev/null,fd 3 被重定向到 stderr 现在指向的位置,然后 stderr 被重定向到/dev/null.假设 stderr 最初指向终端,则结果是3 -> terminal, 2 -> /dev/null。这些重定向适用于组。

如果我们{ foo 2>&3 & }在组内,则foo在后台启动,其stderr指向组的fd 3,并且作业id和进程id被打印到组的stderr。

将这两者放在一起,foo的 stderr 指向该组的 fd 3 指向的位置,即原始 stderr,并且作业 id 打印到该组的 fd 2,即/dev/null

因此,实际上,{ 2>&3 foo & } 3>&2 2>/dev/null将 shell 打印的作业 ID 和进程 ID 重定向到/dev/null,并foo围绕此重定向路由 的 stderr:

foo's stdout      -> group's fd 1 -> original stdout (never redirected)
foo's stderr      -> group's fd 3 -> original stderr
job id from group -> group's fd 2 -> /dev/null

答案3

您错过了该{命令以 nulled 运行stderr,它使用恢复的 fd 启动包含的后台命令,但它是启动命令并发出命令启动的反馈消息的命令,并且它以 nulled 运行。

相关内容