当我的顶级脚本退出时,我正在寻找一种清理混乱的方法.
特别是如果我想使用set -e
,我希望后台进程会在脚本退出时死掉.
要清理一些乱七八糟的东西,trap
可以使用.它可以提供特定信号到达时执行的内容列表:
trap "echo hello" SIGINT
但是如果shell退出,也可以用来执行某些操作:
trap "killall background" EXIT
它是内置的,因此help trap
会为您提供信息(与bash一起使用).如果你只想杀死后台工作,你可以这样做
trap 'kill $(jobs -p)' EXIT
注意使用单一'
,以防止外壳$()
立即替换.
这对我有用(感谢评论者的改进):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
kill -- -$$
将SIGTERM发送到整个进程组,从而杀死后代.
EXIT
使用时指定信号很有用set -e
(这里有更多细节).
更新:https://stackoverflow.com/a/53714583/302079通过添加退出状态和清除功能来改进此功能.
trap "exit" INT TERM trap "kill 0" EXIT
为什么要转换INT
并TERM
退出?因为两者都应该触发kill 0
而不进入无限循环.
为什么引发kill 0
的EXIT
?因为正常的脚本退出也应该触发kill 0
.
为什么kill 0
?因为嵌套的子壳也需要被杀死.这将取消整个流程树.
陷阱'kill $(jobs -p)'退出
我只会对Johannes的回答进行微小的更改,并使用jobs -pr将kill限制为正在运行的进程,并向列表中添加更多信号:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
trap 'kill 0' SIGINT SIGTERM EXIT
在@ tokland的答案中描述的解决方案非常好,但是最新的Bash 在使用时崩溃并出现了分段错误.这是因为Bash从v.4.3开始,允许陷阱递归,在这种情况下变为无限:
壳进程临危SIGINT
或SIGTERM
或EXIT
;
信号被捕获,执行kill 0
,发送SIGTERM
到组中的所有进程,包括shell本身;
去1 :)
这可以通过手动取消注册陷阱来解决:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
更奇特的方式,允许打印收到的信号,并避免"终止:"消息:
#!/usr/bin/env bash
trap_with_arg() { # from /sf/ask/17360801/
local func="$1"; shift
for sig in "$@"; do
trap "$func $sig" "$sig"
done
}
stop() {
trap - SIGINT EXIT
printf '\n%s\n' "recieved $1, killing children"
kill -s SIGINT 0
}
trap_with_arg 'stop' EXIT SIGINT SIGTERM SIGHUP
{ i=0; while (( ++i )); do sleep 0.5 && echo "a: $i"; done } &
{ i=0; while (( ++i )); do sleep 0.6 && echo "b: $i"; done } &
while true; do read; done
UPD:添加了最小的例子; 改进了stop
功能以避免去除不必要的信号并隐藏输出中的"已终止:"消息.感谢Trevor Boyd Smith的建议!
为了安全起见,我发现最好定义一个清理函数并从陷阱中调用它:
cleanup() { local pids=$(jobs -pr) [ -n "$pids" ] && kill $pids } trap "cleanup" INT QUIT TERM EXIT [...]
或者完全避免这个功能:
trap '[ -n "$(jobs -pr)" ] && kill $(jobs -pr)' INT QUIT TERM EXIT [...]
为什么?因为只需使用trap 'kill $(jobs -pr)' [...]
一个假定存在将是当陷阱条件获得信号,运行后台作业.当没有工作时,人们会看到以下(或类似)消息:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
因为jobs -pr
是空的 - 我结束了'陷阱'(双关语).