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

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


当前回答

通用解决方案,也在sh中工作(工作不输出任何东西到stdout):

trap "pkill -P $$" EXIT INT

其他回答

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,但增加了退出代码

在@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的建议!

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

我把@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

这对我来说是可行的(多亏了评论的改进):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT

kill——-$$向整个进程组发送一个SIGTERM,因此也杀死后代。 在使用set -e时,指定信号EXIT很有用(更多细节请点击这里)。