web-dev-qa-db-ja.com

シェルスクリプトの終了時にバックグラウンドプロセス/ジョブを強制終了するにはどうすればよいですか?

トップレベルのスクリプトが終了したときに混乱をクリーンアップする方法を探しています。

特にset -eを使用したい場合は、スクリプトの終了時にバックグラウンドプロセスが停止することを望みます。

168
elmarco

いくつかの混乱をクリーンアップするには、trapを使用できます。特定のシグナルが到着したときに実行されるもののリストを提供できます。

trap "echo hello" SIGINT

ただし、シェルが終了した場合、何かを実行するために使用することもできます。

trap "killall background" EXIT

これは組み込みであるため、help trapは情報を提供します(bashで動作します)。バックグラウンドジョブを強制終了する場合は、次の操作を実行できます。

trap 'kill $(jobs -p)' EXIT

単一の'を使用して、シェルが$()をすぐに置き換えないように注意してください。

163

これは私のために働いています(コメント者のおかげで改善されました):

trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
  • kill -- -$$SIGTERM をプロセスグループ全体に送信するため、子孫も殺します。

  • シグナルEXITを指定すると、set -eを使用するときに便利です(詳細は here )。

148
tokland

更新: https://stackoverflow.com/a/53714583/302079 は、終了ステータスとクリーンアップ機能を追加することでこれを改善します。

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

なぜINTTERMを変換して終了するのですか?両方とも無限ループに入ることなくkill 0をトリガーする必要があるためです。

EXITkill 0をトリガーする理由通常のスクリプト終了でもkill 0がトリガーされるためです。

なぜkill 0?ネストされたサブシェルも削除する必要があるためです。これにより、ダウンします プロセスツリー全体

100
korkman

トラップ 'kill $(jobs -p)' EXIT

Johannesの答えにわずかな変更を加えるだけで、jobs -prを使用してkillを実行中のプロセスに制限し、リストにいくつかのシグナルを追加します。

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

@ tokland's answer で説明されているtrap 'kill 0' SIGINT SIGTERM EXITソリューションは本当に素晴らしいですが、最新のBash セグメンテーションフォールトでクラッシュする を使用するときは。これは、v。4.3から始まるBashがトラップ再帰を許可するためです。トラップ再帰はこの場合無限になります。

  1. シェルプロセスはSIGINTまたはSIGTERMまたはEXITを受け取ります。
  2. 信号がトラップされ、kill 0が実行され、SIGTERMがシェル自体を含むグループ内のすべてのプロセスに送信されます。
  3. 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' "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機能を改善して、不要な信号のトラップ除去を行い、出力から「Terminated:」メッセージを非表示にしました。 Trevor Boyd Smith 提案をありがとう!

13
skozin

安全のために、クリーンアップ関数を定義してトラップから呼び出す方が良いと思います。

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)' [...]を使用することにより、トラップ条件が通知されたときにwillバックグラウンドジョブが実行されていると想定されるためです。ジョブがない場合、次の(または同様の)メッセージが表示されます。

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

jobs -prが空だからです-私はその「トラップ」で終わりました(意図されたしゃれ)。

7
tdaitx

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

ジョブには孫プロセスが含まれないことに注意してください。

2
Orsiris de Jong
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 に似ていますが、exit-codeが追加されています

1
Delaware

もう1つのオプションは、スクリプトをプロセスグループリーダーとして設定し、終了時にプロセスグループにkillpgをトラップすることです。

1
orip

そのため、スクリプトの読み込みをスクリプト化します。スクリプトが終了するとすぐに実行されるkillall(またはOSで利用可能なもの)コマンドを実行します。

0
Oli

jobs -pは、サブシェルで呼び出された場合、おそらく出力がファイルではなくパイプではなくリダイレ​​クトされない限り、すべてのシェルで機能しません。 (元々はインタラクティブな使用のみを目的としていたと思います。)

以下はどうですか:

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

Debianのダッシュシェルでは「jobs」の呼び出しが必要です。これは、現在のジョブ(「%%」)が欠落していると更新に失敗します。

0
michaeljt

@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

動作の例:

$ 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
0
nh2

多様性のために、 https://stackoverflow.com/a/2173421/102484 のバリエーションを投稿します。なぜなら、その解決策は私の環境でメッセージ「Terminated」につながるからです:

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