当我的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 

为什么要转换INTTERM退出? 因为两者都应该触发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,在这种情况下变成无限的:

  1. shell进程收到SIGINTSIGTERMEXIT ;
  2. 信号被捕获,执行kill 0 ,向组中的所有进程发送SIGTERM ,包括shell本身;
  3. 去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 :增加了最小的例子; 改进的stopfunction可以消除不必要的信号,并从输出中隐藏“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 

请注意,工作不包括盛大的儿童进程。