答案有点晚,但对我来说,kill 0 或 kill $(jobs -p) 之类的解决方案太过分了(杀死所有子进程)。
如果您只是想确保整理一个特定的子进程(及其自己的子进程),那么更好的解决方案是使用子进程的 PID 按进程组 (PGID) 杀死,如下所示:
set -m
./some_child_script.sh &
some_pid=$!
kill -- -${some_pid}
首先,set -m 命令将启用作业管理(如果尚未启用),这很重要,否则所有命令、子 shell 等将被分配到与您的父脚本相同的进程组(与您在终端中手动运行命令不同),并且 kill 只会给出“没有这样的进程”错误。这需要在您运行您希望作为一个组进行管理的后台命令之前调用(或者如果您有多个,则只需在脚本启动时调用它)。
其次,注意kill 的参数是否定的,这表明你要杀死整个进程组。默认情况下,进程组 ID 与组中的第一个命令相同,因此我们只需在使用 $! 获取的 PID 前添加一个减号即可获得它。如果您需要在更复杂的情况下获取进程组 ID,则需要使用 ps -o pgid= ${some_pid},然后在其中添加减号。
最后,注意选项 -- 的显式结尾的使用,这很重要,否则进程组参数将被视为选项(信号号),kill 会抱怨它没有足够的论据。只有当进程组参数是您希望终止的第一个参数时,您才需要此参数。
这是一个后台超时过程的简化示例,以及如何尽可能多地清理:
#!/bin/bash
# Use the overkill method in case we're terminated ourselves
trap 'kill $(jobs -p | xargs)' SIGINT SIGHUP SIGTERM EXIT
# Setup a simple timeout command (an echo)
set -m
{ sleep 3600; echo "Operation took longer than an hour"; } &
timeout_pid=$!
# Run our actual operation here
do_something
# Cancel our timeout
kill -- -${timeout_pid} >/dev/null 2>&1
wait -- -${timeout_pid} >/dev/null 2>&1
printf '' 2>&1
这应该在所有合理的情况下干净地处理取消这个简单的超时;唯一无法处理的情况是脚本被立即终止 (kill -9),因为它没有机会清理。
我还添加了一个wait,后跟一个无操作(printf ''),这是为了抑制可能由kill 命令引起的“终止”消息,这有点像hack ,但根据我的经验是足够可靠的。