当我的shell脚本退出时,如何终止后台进程/作业?

时间:2008-12-11 17:33:01

标签: shell

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

特别是如果我想使用set -e,我希望当脚本退出时后台进程会死掉。

13 个答案:

答案 0 :(得分:160)

要清理一些乱七八糟的东西,可以使用trap。它可以提供特定信号到达时执行的内容列表:

trap "echo hello" SIGINT

但如果shell退出,也可用于执行某些操作:

trap "killall background" EXIT

它是内置的,因此help trap会为您提供信息(与bash一起使用)。如果您只想杀死后台作业,可以执行

trap 'kill $(jobs -p)' EXIT

注意使用单',以防止shell立即替换$()

答案 1 :(得分:142)

这对我有用(感谢评论者的改进):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$向整个流程组发送SIGTERM,从而杀死了后代。

  • 使用EXIT时,指定信号set -e很有用(更多详情here)。

答案 2 :(得分:99)

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

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

为什么要将INTTERM转换为退出?因为两者都应该触发kill 0而不进入无限循环。

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

为什么kill 0?因为嵌套的子壳也需要被杀死。这将取消the whole process tree

答案 3 :(得分:21)

  陷阱'kill $(jobs -p)'退出

我只会对Johannes的回答进行微小的更改,并使用jobs -pr将kill限制为正在运行的进程并向列表中添加更多信号:

trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT

答案 4 :(得分:12)

@tokland's answer中描述的trap 'kill 0' SIGINT SIGTERM EXIT解决方案非常好,但在使用时最新Bash crashes with a segmantation fault。那是因为从v.4.3开始,Bash允许陷阱递归,在这种情况下它变为无限:

  1. shell进程收到SIGINTSIGTERMEXIT;
  2. 信号被捕获,执行kill 0,将SIGTERM发送给组中的所有进程,包括shell本身;
  3. 转到1:)
  4. 这可以通过手动取消注册陷阱来解决:

    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函数以避免去除不必要的信号并从输出中隐藏“已终止:”消息。感谢Trevor Boyd Smith提出建议!

答案 5 :(得分:7)

为了安全起见,我发现最好定义一个清理函数并从陷阱中调用它:

cleanup() {
        local pids=$(jobs -pr)
        [ -n "$pids" ] && kill $pids
}
trap "cleanup" INT QUIT TERM EXIT [...]

或完全避免使用该功能:

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是空的 - 我以'陷阱'结束了(双关语)。

答案 6 :(得分:2)

在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

请注意,工作不包括大孩子流程。

答案 7 :(得分:1)

另一种选择是让脚本将自己设置为流程组负责人,并在退出时在流程组中捕获killpg。

答案 8 :(得分:1)

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

答案 9 :(得分:0)

因此脚本加载脚本。运行脚本完成后立即执行的killall(或您的操作系统上可用的任何内容)命令。

答案 10 :(得分:0)

如果在子shell中调用,

jobs -p在所有shell中都不起作用,可能除非将其输出重定向到文件而不是管道。 (我认为它最初仅用于交互式使用。)

以下内容如何:

trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]

Debian的破折号外壳需要调用“作业”,如果缺少,则无法更新当前作业(“%%”)。

答案 11 :(得分:0)

当我注意到如果我正在运行前台进程时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

答案 12 :(得分:0)

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

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