当我的shell脚本退出时,如何杀死后台进程/作业?
我正在寻找一种方法来清理我的顶级脚本退出时的混乱。
特别是如果我想使用set -e
,我希望当脚本退出时后台进程会死掉。
为了清理一些烂摊子,可以使用trap
。 它可以提供一个特定的信号到达时执行的东西的列表:
trap "echo hello" SIGINT
但是如果shell退出,也可以用来执行:
trap "killall background" EXIT
这是一个内build的,所以help trap
会给你的信息(与bash工作)。 如果你只想杀背景作业,你可以做
trap 'kill $(jobs -p)' EXIT
注意使用单个'
,以防止shell立即replace$()
。
这对我很有用(感谢评论者):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
-
kill -- -$$
向整个进程组发送一个SIGTERM ,从而杀死子孙。 -
指定信号
EXIT
在使用set -e
(更多细节在这里 )时很有用。
trap "exit" INT TERM trap "kill 0" EXIT
为什么要转换INT
和TERM
退出? 因为两者都应该触发kill 0
而不进入无限循环。
为什么在EXIT
上触发kill 0
? 因为正常的脚本出口也应该触发kill 0
。
为什么kill 0
? 因为嵌套的子壳也需要被杀死。 这将取消整个过程树 。
陷阱“杀死$(作业-p)”退出
我只会对Johannes的答案做一些小的修改,然后使用jobs -pr来限制杀死进程,并在列表中添加更多的信号:
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
@ tokland的答案中描述的trap'kill 0'SIGINT trap 'kill 0' SIGINT SIGTERM EXIT
解决scheme非常好,但是最新的Bash在使用时会出现segmantation错误 。 这是因为从v.4.3开始,Bash允许陷阱recursion,在这种情况下变成无限的:
- shell进程收到
SIGINT
或SIGTERM
或EXIT
; - 信号被捕获,执行
kill 0
,向组中的所有进程发送SIGTERM
,包括shell本身; - 去1 🙂
这可以通过手动注销陷阱来解决:
trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT
更奇特的方式,允许打印接收到的信号,并避免“终止:”消息:
#!/usr/bin/env bash trap_with_arg() { # from https://stackoverflow.com/a/2183063/804678 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
function可以消除不必要的信号,并从输出中隐藏“Terminated:”消息。 感谢Trevor Boyd Smith的build议!
为了安全起见,我觉得最好定义一个清理函数,并从陷阱中调用它:
cleanup() { local pids=$(jobs -pr) [ -n "$pids" ] && kill $pids } trap "cleanup" INT QUIT TERM EXIT [...]
或完全避免该function:
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
是空的 – 我以“陷阱”(双关语)结束。
另一个select是让脚本将自己设置为进程组负责人,并在退出时在进程组中捕获killpg。
所以脚本加载的脚本。 运行脚本完成后立即执行的killall
(或任何可用的OS)命令。
如果在子shell中调用作业,-p在所有shell中都不起作用,可能除非将其输出redirect到文件而不是pipe道。 (我认为它最初只用于交互式使用。)
那下面呢?
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
Debian的dash shell需要调用“jobs”,如果缺less,它将无法更新当前作业(“%%”)。
当我注意到trap
不触发,如果我运行前景,我做了一个@ tokland的答案结合从http://veithen.github.io/2014/11/16/sigterm-propagation.html知识的适应过程(不与;&
背景):
#!/bin/bash # killable-shell.sh: Kills itself and all children (the whole process group) when killed. # Adapted from http://stackoverflow.com/a/2173421 and http://veithen.github.io/2014/11/16/sigterm-propagation.html # Note: Does not work (and cannot work) when the shell itself is killed with SIGKILL, for then the trap is not triggered. trap "trap - SIGTERM && echo 'Caught SIGTERM, sending SIGTERM to process group' && kill -- -$$" SIGINT SIGTERM EXIT echo $@ "$@" & PID=$! wait $PID trap - SIGINT SIGTERM EXIT wait $PID
它的工作范例:
$ bash killable-shell.sh sleep 100 sleep 100 ^Z [1] + 31568 suspended bash killable-shell.sh sleep 100 $ ps aux | grep "sleep" niklas 31568 0.0 0.0 19640 1440 pts/18 T 01:30 0:00 bash killable-shell.sh sleep 100 niklas 31569 0.0 0.0 14404 616 pts/18 T 01:30 0:00 sleep 100 niklas 31605 0.0 0.0 18956 936 pts/18 S+ 01:30 0:00 grep --color=auto sleep $ bg [1] + 31568 continued bash killable-shell.sh sleep 100 $ kill 31568 Caught SIGTERM, sending SIGTERM to process group [1] + 31568 terminated bash killable-shell.sh sleep 100 $ ps aux | grep "sleep" niklas 31717 0.0 0.0 18956 936 pts/18 S+ 01:31 0:00 grep --color=auto sleep
一个很好的版本,在Linux,BSD和MacOS X下运行。首先尝试发送SIGTERM,如果不成功,则在10秒后终止进程。
KillJobs() { for job in $(jobs -p); do kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &) done } TrapQuit() { # Whatever you need to clean here KillJobs } trap TrapQuit EXIT
请注意,工作不包括盛大的儿童进程。