トップレベルのスクリプトが終了したときに混乱をクリーンアップする方法を探しています。
特にset -e
を使用したい場合は、スクリプトの終了時にバックグラウンドプロセスが停止することを望みます。
いくつかの混乱をクリーンアップするには、trap
を使用できます。特定のシグナルが到着したときに実行されるもののリストを提供できます。
trap "echo hello" SIGINT
ただし、シェルが終了した場合、何かを実行するために使用することもできます。
trap "killall background" EXIT
これは組み込みであるため、help trap
は情報を提供します(bashで動作します)。バックグラウンドジョブを強制終了する場合は、次の操作を実行できます。
trap 'kill $(jobs -p)' EXIT
単一の'
を使用して、シェルが$()
をすぐに置き換えないように注意してください。
更新: https://stackoverflow.com/a/53714583/302079 は、終了ステータスとクリーンアップ機能を追加することでこれを改善します。
trap "exit" INT TERM
trap "kill 0" EXIT
なぜINT
とTERM
を変換して終了するのですか?両方とも無限ループに入ることなくkill 0
をトリガーする必要があるためです。
EXIT
でkill 0
をトリガーする理由通常のスクリプト終了でもkill 0
がトリガーされるためです。
なぜkill 0
?ネストされたサブシェルも削除する必要があるためです。これにより、ダウンします プロセスツリー全体 。
トラップ 'kill $(jobs -p)' EXIT
Johannesの答えにわずかな変更を加えるだけで、jobs -prを使用してkillを実行中のプロセスに制限し、リストにいくつかのシグナルを追加します。
trap 'kill $(jobs -pr)' SIGINT SIGTERM EXIT
@ tokland's answer で説明されているtrap 'kill 0' SIGINT SIGTERM EXIT
ソリューションは本当に素晴らしいですが、最新のBash セグメンテーションフォールトでクラッシュする を使用するときは。これは、v。4.3から始まるBashがトラップ再帰を許可するためです。トラップ再帰はこの場合無限になります。
SIGINT
またはSIGTERM
またはEXIT
を受け取ります。kill 0
が実行され、SIGTERM
がシェル自体を含むグループ内のすべてのプロセスに送信されます。これは、トラップを手動で登録解除することで回避できます。
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 提案をありがとう!
安全のために、クリーンアップ関数を定義してトラップから呼び出す方が良いと思います。
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
が空だからです-私はその「トラップ」で終わりました(意図されたしゃれ)。
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
ジョブには孫プロセスが含まれないことに注意してください。
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つのオプションは、スクリプトをプロセスグループリーダーとして設定し、終了時にプロセスグループにkillpgをトラップすることです。
そのため、スクリプトの読み込みをスクリプト化します。スクリプトが終了するとすぐに実行されるkillall
(またはOSで利用可能なもの)コマンドを実行します。
jobs -pは、サブシェルで呼び出された場合、おそらく出力がファイルではなくパイプではなくリダイレクトされない限り、すべてのシェルで機能しません。 (元々はインタラクティブな使用のみを目的としていたと思います。)
以下はどうですか:
trap 'while kill %% 2>/dev/null; do jobs > /dev/null; done' INT TERM EXIT [...]
Debianのダッシュシェルでは「jobs」の呼び出しが必要です。これは、現在のジョブ(「%%」)が欠落していると更新に失敗します。
@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
多様性のために、 https://stackoverflow.com/a/2173421/102484 のバリエーションを投稿します。なぜなら、その解決策は私の環境でメッセージ「Terminated」につながるからです:
trap 'test -z "$intrap" && export intrap=1 && kill -- -$$' SIGINT SIGTERM EXIT