代码之家  ›  专栏  ›  技术社区  ›  elmarco

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

  •  153
  • elmarco  · 技术社区  · 16 年前

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

    尤其是如果我想用 set -e ,我希望脚本退出时后台进程将停止。

    13 回复  |  直到 6 年前
        1
  •  158
  •   Johannes Schaub - litb    16 年前

    清理一些烂摊子, trap 可以使用。它可以提供特定信号到达时执行的内容列表:

    trap "echo hello" SIGINT
    

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

    trap "killall background" EXIT
    

    它是内置的,所以 help trap 将为您提供信息(与bash一起使用)。如果你只想杀死后台工作,你可以这样做

    trap 'kill $(jobs -p)' EXIT
    

    注意使用单人房 ' ,以防止外壳替换 $() 立即。

        2
  •  135
  •   tokland    10 年前

    这对我很有用(感谢评论改善了):

    trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
    
    • kill -- -$$ 发送一个 SIGTERM 到了整个过程群,因此也杀掉了后代。

    • 指定信号 EXIT 使用时很有用 set -e (更多细节) here )

        3
  •  97
  •   Community CDub    8 年前
    trap "exit" INT TERM
    trap "kill 0" EXIT
    

    为什么转换 INT TERM 退出?因为两者都会触发 kill 0 不进入无限循环。

    为什么触发 杀戮0 EXIT ?因为普通脚本出口应该触发 杀戮0 也是。

    为什么? 杀戮0 ?因为嵌套的子shell也需要被杀死。这个会取下来的 the whole process tree .

        4
  •  20
  •   Cody Gray    14 年前

    陷阱“kill$(jobs-p)”退出

    我只会对johannes的答案做一些细微的更改,并使用jobs-pr来限制对正在运行的进程的杀戮,并在列表中添加更多的信号:

    trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
    
        5
  •  11
  •   Community CDub    8 年前

    这个 trap 'kill 0' SIGINT SIGTERM EXIT 中描述的解决方案 @tokland's answer 很不错,但最近的狂欢 crashes with a segmantation fault 使用时。这是因为bash,从v._4.3开始,允许陷阱递归,在这种情况下,陷阱递归将变为无穷大:

    1. Shell进程接收 SIGINT SIGTERM EXIT ;
    2. 信号被捕获,执行 kill 0 发送 西格尔 集团内的所有流程,包括壳牌公司本身;
    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 :添加了最小示例;改进了 stop 用于排除不必要的信号并隐藏输出的“已终止”消息的功能。谢谢 Trevor Boyd Smith 为了建议!

        6
  •  7
  •   tdaitx    12 年前

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

    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 是空的-我结束了那个“陷阱”(双关语的意思)。

        7
  •  2
  •   Orsiris de Jong    9 年前

    一个在Linux、BSD和MacOSX下工作的好版本。首先尝试发送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
    

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

        8
  •  1
  •   orip    16 年前

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

        9
  •  0
  •   Oli    16 年前

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

        10
  •  0
  •   michaeljt    12 年前

    如果在子shell中调用jobs-p,则它不会在所有shell中工作,除非它的输出被重定向到一个文件而不是管道中。(我假设它最初只用于交互使用。)

    下面是什么?

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

    对“job s”的调用需要使用debian的dash shell,如果缺少,它将无法更新当前作业(“%%)。

        11
  •  0
  •   nh2    10 年前

    我将@tokland的答案与 http://veithen.github.io/2014/11/16/sigterm-propagation.html 当我注意到 trap 如果我正在运行前台进程(而不是使用 & ):

    #!/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
    

    IT工作示例:

    $ 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
  •   Delaware    6 年前
    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 ,但添加了退出代码

        13
  •  0
  •   noonex    6 年前

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

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