通常,当作业在后台启动时,jobs
作业完成后第一次运行时会报告已完成,而后续执行则不会报告任何内容:
$ ping -c 4 localhost &>/dev/null &
[1] 9666
$ jobs
[1]+ Running ping -c 4 localhost &> /dev/null &
$ jobs
[1]+ Done ping -c 4 localhost &> /dev/null
$ jobs ## returns nothing
$
但是,当在脚本内的子 shell 中运行时,它似乎总是返回一个值。该脚本永远不会退出:
#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
sleep 1;
done
如果我tee
在[[ ]]
构造中使用来查看 的输出jobs
,我会发现它总是打印该Done ...
行。正如我所期望的那样,不仅是一次,而且显然是永远。
更奇怪的是,jobs
在循环内运行会导致它按预期退出:
#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
jobs
sleep 1;
done
最后,正如 @mury 所指出的,第一个脚本按预期工作,如果从命令行运行则退出:
$ ping -c 5 localhost &>/dev/null &
[1] 13703
$ while [[ -n $(jobs) ]]; do echo -n . ; sleep 1; done
...[1]+ Done ping -c 5 localhost &> /dev/null
$
这是我在回答一个问题时出现的问题关于超级用户,所以请不要发布建议更好的方法来执行该循环的答案。我自己也能想到一些。我好奇的是
为什么在构造
jobs
中表现不同[[ ]]
?为什么它总是返回该Done...
行,而手动运行时却不会返回该行?为什么
jobs
在循环内运行会改变脚本的行为?
答案1
当然,您知道这$(…)
会导致括号内的命令在子 shell 中运行。当然,您知道这jobs
是一个 shell 内置函数。嗯,看起来像jobs
一旦 shell 死亡,它就会从内存中清除一个作业已经被报告了。但是,当您运行 时$(jobs)
,该jobs
命令在子 shell 中运行,因此它没有机会告诉父 shell(运行脚本的 shell)ping
已报告后台作业(在您的示例中为 )的死亡。因此,每次 shell 生成一个子 shell 来运行 thingie 时$(jobs)
,该子 shell 仍然具有完整的作业列表(即,该ping
作业在那里,即使它在第 5 次迭代后就死掉了),因此jobs
仍然(再次)相信它需要报告ping
作业的状态(即使它在过去四秒内已经停止)。
这解释了为什么jobs
在循环中运行纯粹的命令会导致它按预期退出:一旦运行jobs
在父 shell 中,父 shell 知道作业的终止已经被报告了给用户。
为什么在交互式 shell 中会有所不同?因为,每当交互式 shell 的前台子进程终止时,shell 都会报告在 前台进程运行时已终止的任何后台作业1 。因此,在运行ping
时终止sleep 1
,并且当sleep
终止时,shell 报告后台作业已终止。等等瞧。
1更准确的说法可能是“在前台进程运行时状态已更改的任何后台作业”。我相信它还可能报告已暂停的作业(kill -SUSP
,相当于Ctrl+ 的编程Z)或未暂停的作业(kill -CONT
,这是该fg
命令的作用)。