ter*_*don 4 shell bash shell-script jobs
正常情况下,当一个job在后台启动时,在jobjobs
完成后第一次运行会报告它已经完成,并且后续执行什么都不做:
$ 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
$
Run Code Online (Sandbox Code Playgroud)
但是,当在脚本的子 shell 中运行时,它似乎总是返回一个值。这个脚本永远不会退出:
#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
sleep 1;
done
Run Code Online (Sandbox Code Playgroud)
如果我tee
在[[ ]]
构造中使用来查看 的输出jobs
,我会看到它总是在打印该Done ...
行。正如我所料,不仅一次,而且显然是永远。
更奇怪的是,jobs
在循环中运行会导致它按预期退出:
#!/usr/bin/env bash
ping -c 3 localhost &>/dev/null &
while [[ -n $(jobs) ]]; do
jobs
sleep 1;
done
Run Code Online (Sandbox Code Playgroud)
最后,正如@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
$
Run Code Online (Sandbox Code Playgroud)
这是在我回答关于超级用户的问题时出现的,所以请不要发布推荐更好的方法来做循环的答案。我自己能想到几个。我很好奇的是
为什么jobs
在[[ ]]
构造中的行为不同?为什么它总是会返回该Done...
行,而手动运行时却不会?
为什么jobs
在循环内运行会改变脚本的行为?
当然,您知道这$(…)
会导致括号内的命令在子shell 中运行。当然,你知道这jobs
是一个内置的 shell。好吧,jobs
一旦报告了它的死亡,它看起来就像从 shell 的内存中清除了一个作业。但是,当您运行时$(jobs)
,该jobs
命令在子shell中运行,因此它没有机会告诉父shell(正在运行脚本的那个ping
)已经报告了后台作业的死亡(在您的示例中) . 所以,每次 shell 产生一个子 shell 来运行这个$(jobs)
东西时,这个子 shell 仍然有一个完整的作业列表(即,ping
作业在那里,即使它在第 5 次迭代后就死了),等等jobs
仍然(再次)认为它需要报告ping
工作(即使它在过去四秒钟内已经死亡)。
这解释了为什么jobs
在循环中运行一个纯粹的命令会导致它按预期退出:一旦您jobs
在父 shell 中运行,父 shell 就知道作业的终止已报告给用户。
为什么它在交互式 shell 中有所不同?因为,每当交互式 shell 的前台子
进程终止时,shell 都会报告在前台进程运行时已终止的任何后台作业1。所以,ping
当终止sleep 1
正在运行,而当sleep
终止,在后台作业的死壳报告。等等。
1说“在前台进程运行时改变状态的任何后台作业”可能更准确。我相信它也可能报告已暂停(kill -SUSP
,相当于Ctrl+的程序化Z)或未暂停(kill -CONT
,这是fg
命令所做的)的作业。