几周前,我看到一个奇怪的答案关于这个问题“(如何)在后台静默启动任务?“。这个解决方案似乎不正确(比照我的回答)尽管 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 background
cmd&
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/null
。grep
不会覆盖任何重定向,因此其标准错误与 相同{...}
,并且被重定向到/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
)。
当命令被分组时,重定向可以应用于整个命令列表。
我们可以验证应用的重定向组是否也重定向了作业 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 运行。