我正在寻找一种方法来清理混乱时,我的顶级脚本退出。

特别是如果我想使用set -e,我希望后台进程在脚本退出时结束。


当前回答

只是为了多样性,我将发布https://stackoverflow.com/a/2173421/102484的变体,因为这个解决方案在我的环境中会导致消息“终止”:

trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT

其他回答

一个可以在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

请注意,作业不包括子代进程。

在@tokland的回答中描述的陷阱“kill 0”SIGINT SIGTERM EXIT解决方案真的很好,但最新的Bash在使用它时崩溃了,分割错误。这是因为Bash,从v. 4.3开始,允许陷阱递归,在这种情况下,它变得无限:

shell进程接收到SIGINT或SIGTERM或EXIT; 信号被捕获,执行kill 0,将SIGTERM发送给组中的所有进程,包括shell本身; 转到第1节

这可以通过手动注销陷阱来解决:

trap 'trap - SIGTERM && kill 0' SIGINT SIGTERM EXIT

允许打印接收到的信号并避免“Terminated:”消息的更花哨的方式:

#!/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' "received $1, killing child processes"
  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:增加了一个最小的例子;改进的停止功能,以避免去捕获不必要的信号,并从输出中隐藏“Terminated:”消息。感谢Trevor Boyd Smith的建议!

function cleanup_func {
    sleep 0.5
    echo cleanup
}

trap "exit \$exit_code" INT TERM
trap "exit_code=\$?; cleanup_func; kill 0" EXIT

# exit 1
# exit 0

类似于https://stackoverflow.com/a/22644006/10082476,但增加了退出代码

更新:https://stackoverflow.com/a/53714583/302079通过添加退出状态和清除功能改进了这一点。

trap "exit" INT TERM
trap "kill 0" EXIT

为什么转换INT和TERM退出?因为两者都应该在不进入无限循环的情况下触发kill 0。

为什么在EXIT时触发kill 0 ?因为正常的脚本退出也应该触发kill 0。

为什么要杀死0?因为嵌套的子shell也需要被杀死。这会把整个过程树都画下来。

脚本的加载。运行一个killall(或操作系统上可用的任何命令)命令,该命令在脚本完成后立即执行。