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


193

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

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

回答:


186

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

trap "echo hello" SIGINT

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

trap "killall background" EXIT

これは組み込みなので、help trap情報を提供します(bashで動作します)。バックグラウンドジョブのみを殺したい場合は、

trap 'kill $(jobs -p)' EXIT

'シェルが$()即座に置換されないように、singleを使用することに注意してください。


では、どのようにしてすべての子供だけを殺すのですか?(または私は明らかな何かを見逃していますか)
elmarco 2008

18
killallはあなたの子供を殺しますが、あなたは殺しません
orip 2008

3
kill $(jobs -p)サブシェルでコマンド置換を実行するため、ダッシュでは機能しません(ダッシュのコマンド置換を参照)
user1431317

7
されるkillall backgroundプレースホルダことになって?backgroundはマニュアルページにはありません...
エヴァン・ベン

171

これは私にとってはうまくいきます(コメントのおかげで改善されました):

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

  • 信号を指定するEXITと、使用時に役立ちますset -e(詳細はこちら)。


1
全体的にうまく機能するはずですが、子プロセスはプロセスグループを変更する可能性があります。一方、ジョブ制御は不要であり、一部の孫プロセスが他のソリューションで見逃される可能性もあります。
michaeljt

5
"kill 0"は親のbashスクリプトもkillすることに注意してください。「kill--$ BASHPID」を使用して、現在のスクリプトの子だけをkillすることができます。bashバージョンに$ BASHPIDがない場合は、BASHPID = $(sh -c 'echo $ PPID')
ACyclic

2
素敵で明確な解決策をありがとう!残念ながら、Bash 4.3にセグメンテーション違反が発生し、トラップの再帰が可能になります。私は4.3.30(1)-releaseOSXでこれに遭遇し、Ubuntuでも確認されています。しかし、obvoius wokaroundがあります:)
skozin

4
よくわかりません-$$。'-<PID> `と評価され-1234ます。killマンページ//組み込みマンページでは、先頭のダッシュは送信するシグナルを指定します。ただし、おそらくそれをブロックしますが、それ以外の場合、先頭のダッシュは文書化されていません。何か助けは?
Evan Benn

4
@EvanBenn:チェックしますman 2 kill。これは、PIDが負の場合、信号が、指定されたID(en.wikipedia.org/wiki/Process_group)を持つプロセスグループ内のすべてのプロセスに送信されることを説明します。これがman 1 killまたはman bashで言及されておらず、ドキュメントのバグと見なされる可能性があることは混乱を招きます。
user001

111

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

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

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

なぜトリガーkill 0するのEXITですか?通常のスクリプト出口がトリガーする必要があるためkill 0もあるためです。

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


3
Debianでの私のケースに対する唯一の解決策。
MindlessRanger 2015

3
Johannes Schaubの回答もtoklandの回答も、シェルスクリプトが開始したバックグラウンドプロセス(Debian上)を強制終了できませんでした。このソリューションは機能しました。なぜこの回答がこれ以上賛成されないのかわかりません。正確に何をkill 0意味し、何をしているのかについてもっと詳しく説明してもらえますか?
josch 2016年

7
これはすばらしいですが、親シェルも強制終了します:-(
vidstige 2017年

5
このソリューションは文字通り過剰です。kill 0(スクリプト内)がXセッション全体を台無しにしました!場合によってはkill 0が役立つこともありますが、これは一般的な解決策ではないという事実を変えるものではないため、使用する十分な理由がない限り、可能であれば回避する必要があります。スクリプトのバックグラウンドジョブだけでなく、親シェルまたはXセッション全体を強制終了する可能性があるという警告を追加すると便利です。
Lissanro Rayen 2017

3
これは状況によっては興味深い解決策になるかもしれませんが、@ vidstigeで指摘されているように、起動プロセスを含むプロセスグループ全体(つまり、ほとんどの場合は親シェル)が強制終了されます。IDEを介してスクリプトを実行しているときは、間違いなく望ましくありません。
matpen

22

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

私はヨハネスの答えに小さな変更を加え、jobs -prを使用してkillを実行中のプロセスに制限し、いくつかのシグナルをリストに追加します。

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

14

trap 'kill 0' SIGINT SIGTERM EXIT記載された解決策@ toklandの答えは本当に素晴らしいですが、最新のバッシュはsegmantationフォールトでクラッシュし、それを使用した場合。これは、バージョン4.3以降のBashがトラップの再帰を許可するためです。この場合、トラップの再帰は無限になります。

  1. シェルプロセスはSIGINTor 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に感謝します!


stop()あなたのシグナル番号として最初の引数を提供しますが、あなたは信号が登録解除されているものをハードコーディング。登録解除される信号をハードコードするのではなく、最初の引数を使用してstop()関数で登録解除できます(そうすると、他の再帰的信号(ハードコードされた3以外)が停止する可能性があります)。
Trevor Boyd Smith

@TrevorBoydSmith、これは期待どおりに機能しないと思います。たとえば、シェルはで強制終了される可能性SIGINTがありkill 0ますがSIGTERM、を送信すると、再びトラップされます。ただし、SIGTERM2番目のstop呼び出し中にトラップが除去されるため、これは無限再帰を生成しません。
スコジン

おそらく、trap - $1 && kill -s $1 0もっとうまくいくはずです。この回答をテストして更新します。素敵なアイデアをありがとう!:)
スコジン

いいえ、trap - $1 && kill -s $1 0私たちはで殺せないので、あまり機能しませんEXIT。しかし、デフォルトでこのシグナルを送信するTERMため、実際にはdo de-trap で十分killです。
スコジン

で再帰をテストしましEXITた。trapシグナルハンドラは常に1回だけ実行されます。
Trevor Boyd Smith

9

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

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

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

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


このテストケース[ -n "$(jobs -pr)" ]は私のbashでは機能しません。私はGNU bash、バージョン4.2.46(2)-release(x86_64-redhat-linux-gnu)を使用しています。「kill:usage」メッセージがポップアップし続けます。
Douwe van der Leest、

私はそれがjobs -prバックグラウンドプロセスの子のPIDを返さないという事実に関係しているのではないかと思います。プロセスツリー全体を破壊するのではなく、ルートを削除するだけです。
Douwe van der Leest、

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

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



1

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


0

スクリプトの読み込みをスクリプト化します。実行してkillallすぐにスクリプトが終了されると実行する(またはあなたのOS上で利用できるものは何でも)コマンドを使用します。


0

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

以下についてはどうでしょう:

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

「ジョブ」の呼び出しは、現在のジョブ(「%%」)がない場合、現在のジョブ(「%%」)の更新に失敗するDebianのダッシュシェルで必要です。


0

フォアグラウンドプロセスを実行している場合にトリガーされないことに気付いたとき、@ 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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.