次のようなBashスクリプトがある場合:
function repeat {
while :; do
echo repeating; sleep 1
done
}
repeat &
echo running once
running once
は一度印刷されますが、repeat
のフォークは永遠に存続し、無限に印刷されます。
スクリプトを作成したスクリプトが終了した後、repeat
が実行され続けるのを防ぐにはどうすればよいですか?
新しいbash -c
インタープリターを明示的にインスタンス化すると、親が消えたために強制的に終了すると思いましたが、孤立したプロセスはinit
またはPID1によって採用されていると思います。
別のファイルを使用してこれをテストします。
# repeat.bash
while :; do echo repeating; sleep 1; done
# fork.bash
bash -c "./repeat.bash & echo an exiting command"
./fork.bash
を実行すると、repeat.bash
はバックグラウンドで永久に実行され続けます。
単純で怠惰な解決策は、行をfork.bash
に追加することです。
pkill repeat.bash
しかし、その名前の別の重要なプロセスを持たない方がよいでしょう。そうしないと、それも消滅します。
フォークされたシェルでバックグラウンドジョブを処理するためのより良いまたは受け入れられた方法があり、それらを作成したスクリプト(またはプロセス)が終了したときに終了する必要があるのだろうか?
同じ名前のすべてのプロセスを盲目的にpkilling
するよりも良い方法がない場合、Webサーバーのようなものと一緒に実行される繰り返しジョブをどのように処理して終了する必要がありますか? スクリプトはcron
リポジトリにあり、コードは/etc/
のシステムファイルを変更せずに自己完結型である必要があるため、git
ジョブは避けたいです。
これにより、スクリプトが終了する前にバックグラウンドプロセスが強制終了されます。
trap '[ "$pid" ] && kill "$pid"' EXIT
function repeat {
while :; do
echo repeating; sleep 1
done
}
repeat &
pid=$!
echo running once
trap '[ "$pid" ] && kill "$pid"' EXIT
これにより、trapが作成されます。スクリプトが終了しようとすると、一重引用符で囲まれたコマンドが実行されます。このコマンドは、シェル変数pid
に空でない値が割り当てられているかどうかを確認します。含まれている場合は、pid
に関連付けられているプロセスが強制終了されます。
pid=$!
これにより、前のバックグラウンドコマンドのプロセスIDが保存されます(repeat &
)シェル変数pid
内。
Patrick がコメントで指摘しているように、スクリプトが強制終了される可能性がありますafterバックグラウンドプロセスが開始されますが、beforepid
変数が設定されます。この場合は、次のコードで処理できます。
my_exit() {
[ "$racing" ] && pid=$!
[ "$pid" ] && kill "$pid"
}
trap my_exit EXIT
function repeat {
while :; do
echo repeating; sleep 1
done
}
racing=Y
repeat &
pid=$!
racing=
echo running once
tcsh
シェルにはhup
コマンドがあり、終了時にコマンドを強制終了するようにシェルに指示します。
hup mycommand...
ただし、tcsh
は関数をサポートしていないため、関数の実行には役立ちません。
zsh
では、ジョブ制御が有効になっている場合、すべてのバックグラウンドジョブに対してデフォルトで実行されます。
$ zsh -c 'set -m; sleep 2 &'; ps
[1] 23672
zsh:1: warning: 1 jobs SIGHUPed
ただし、スクリプトでジョブ制御を有効にすると、副作用が発生する可能性があります。
bash
も同様に実行できますが、対話型の場合(ただし、スクリプトで-iを使用して呼び出す場合も実行されます)、ログインシェル(!?)の場合にのみ実行できます。そして、huponexit
オプションを有効にする必要があります。そう:
bash -O huponexit -lic 'sleep 2 &'
これにはかなりの数の副作用がありますが、おそらく良い考えではありません。
zsh
のもう1つのオプションは、実行中のすべての既知の子プロセスを終了することです(いずれの場合も、孫について簡単に知ることはできません(また、常に子を殺したいと思うこともありません))。 zsh
は、それを$jobstates
連想配列で利用できるようにします。
TRAPEXIT() kill -s HUP ${${jobstates[(R)running:*]/#*:/}/%=*/}
trap exit INT HUP TERM
他のシェルは子のリストを公開しませんが、次のように、ppidが$$
であるプロセスを強制終了できます。
trap 'pkill -HUP -P "$$"' EXIT INT HUP TERM