web-dev-qa-db-ja.com

シェルフォークがそのイニシエーターより長く生きることを防ぎますか?

次のような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

しかし、その名前の別の重要なプロセスを持たない方がよいでしょう。そうしないと、それも消滅します。

  1. フォークされたシェルでバックグラウンドジョブを処理するためのより良いまたは受け入れられた方法があり、それらを作成したスクリプト(またはプロセス)が終了したときに終了する必要があるのだろうか?

  2. 同じ名前のすべてのプロセスを盲目的にpkillingするよりも良い方法がない場合、Webサーバーのようなものと一緒に実行される繰り返しジョブをどのように処理して終了する必要がありますか? スクリプトはcronリポジトリにあり、コードは/etc/のシステムファイルを変更せずに自己完結型である必要があるため、gitジョブは避けたいです。

3
cat

これにより、スクリプトが終了する前にバックグラウンドプロセスが強制終了されます。

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
4
John1024

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
2