我正在寻找一种方法来清理混乱时,我的顶级脚本退出。
特别是如果我想使用set -e,我希望后台进程在脚本退出时结束。
我正在寻找一种方法来清理混乱时,我的顶级脚本退出。
特别是如果我想使用set -e,我希望后台进程在脚本退出时结束。
当前回答
在@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的建议!
其他回答
一个可以在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的建议!
我终于找到了一个解决方案,在所有情况下都可以递归地终止所有下降,不管它们是作业还是子流程。这里的其他解决方案似乎都失败了,比如:
while ! ffmpeg ....
do
sleep 1
done
在我的情况下,ffmpeg将在父脚本退出后继续运行。
我在这里找到了一个递归地获得所有子进程的pid的解决方案,并在陷阱处理程序中使用它:
cleanup() {
# kill all processes whose parent is this process
kill $(pidtree $$ | tac)
}
pidtree() (
[ -n "$ZSH_VERSION" ] && setopt shwordsplit
declare -A CHILDS
while read P PP;do
CHILDS[$PP]+=" $P"
done < <(ps -e -o pid= -o ppid=)
walk() {
echo $1
for i in ${CHILDS[$1]};do
walk $i
done
}
for i in "$@";do
walk $i
done
)
trap cleanup EXIT
上面放在bash脚本开头的代码成功地杀死了所有子进程。请注意,pidtree是用$$调用的,$$是正在退出的bash脚本的PID,并且PID列表(每行一个)使用tac反转,以尝试确保父进程只在子进程之后被杀死,以避免在循环中可能出现的竞争条件,例如我给出的例子。
更新: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也需要被杀死。这会把整个过程树都画下来。
这对我来说是可行的(多亏了评论的改进):
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
kill——-$$向整个进程组发送一个SIGTERM,因此也杀死后代。 在使用set -e时,指定信号EXIT很有用(更多细节请点击这里)。