为什么“jobs”在脚本内的子 shell 中运行时总是返回已完成进程的一行?

为什么“jobs”在脚本内的子 shell 中运行时总是返回已完成进程的一行?

通常,当作业在后台启动时,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
$ 

这是我在回答一个问题时出现的问题关于超级用户,所以请不要发布建议更好的方法来执行该循环的答案。我自己也能想到一些。我好奇的是

  1. 为什么在构造jobs中表现不同[[ ]]?为什么它总是返回该Done...行,而手动运行时却不会返回该行?

  2. 为什么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命令的作用)。

相关内容