Ctrl + Cを押すと、シェルスクリプトでバックグラウンドプロセスが完了するまでどのように終了して待ちますか?

Ctrl + Cを押すと、シェルスクリプトでバックグラウンドプロセスが完了するまでどのように終了して待ちますか?

Ctrlcバックグラウンドプロセスを実行してシェルスクリプトを使用するときは、サブプロセスを終了してから終了するようにシェルスクリプトを設定しようとしています。

私が考えることができる最善はこれです。kill 0 -INT待機が発生する前にスクリプトも終了するように見えるため、シェルスクリプトは子プロセスが完了する前に終了します。

このシェルスクリプトが送信された後に子が死ぬのを待つ方法についてのアイデアはありますかINT

#!/bin/bash
trap 'killall' INT

killall() {
    echo "**** Shutting down... ****"
    kill 0 -INT
    wait # Why doesn't this wait??
    echo DONE
}

process1 &
process2 &
process3 &

cat # wait forever

答え1

あなたのkill命令は逆さまです。

多くのUNIXコマンドと同様に、マイナス記号で始まるオプションは他のパラメータの前に表示する必要があります。

書くなら

kill -INT 0

-INTオプションとして処理されSIGINT(現在のプロセスグループ内のすべてのプロセスを表す特殊番号)0に送信されます。0

しかし、書いてみると

kill 0 -INT

を表示し、0オプションがないと判断してSIGTERMデフォルト値を使用します。そして送るそれあなたがそうであったように、現在のプロセスグループに

kill -TERM 0 -INT    

SIGTERM(また、送信しようとする-INTと構文エラーが発生しますが、最初に送信SIGTERMして0それ以上到達しません。)

したがって、デフォルトのスクリプトをSIGTERM実行するwait前とecho DONE

次へ追加

trap 'echo got SIGTERM' TERM

トップ、すぐ後ろに

trap 'killall' INT

それを証明するために再実行しました。

Stephane Chazelasが指摘したように、背景がある子供は基本的に無視されprocess1ます。SIGINT

とにかく過ごす方がSIGTERM合理的だと思います。

結局、kill -process group子どもたちに先にあげると言うことはできないようです。ドアを閉めるときは、信号を無視することをお勧めします。

だからこれを試してみてください:

#!/bin/bash
trap 'killall' INT

killall() {
    trap '' INT TERM     # ignore INT and TERM while shutting down
    echo "**** Shutting down... ****"     # added double quotes
    kill -TERM 0         # fixed order, send TERM not INT
    wait
    echo DONE
}

./process1 &
./process2 &
./process3 &

cat # wait forever

答え2

残念ながら、バックグラウンドで開始されたコマンドは、SIGINTを無視するようにシェルによって設定され、悪いことは機能しないことtrapです。

(trap - INT; exec process1) &
(trap - INT; exec process2) &
trap '' INT
wait

Ctrl-Cを押すと、process1とprocess2が同じプロセスグループ(つまり、端末のフォアグラウンドプロセスグループ)に属するため、SIGINTを受け取るためです。

上記のコードは、この点でPOSIXと互換性のないpdkshとzshで使用されます。

他のシェルの場合は、次のような他の方法を使用してSIGINTのデフォルトハンドラを復元する必要があります。

perl -e '$SIG{INT}=DEFAULT; exec "process1"' &

または、SIGTERMなどの他の信号を使用してください。

答え3

いくつかのバックグラウンドプロセスを管理したい場合は、bashジョブ制御機能を試してみてはいかがでしょうか?

$ gedit &
[1] 2581
$ emacs &
[2] 2594
$ jobs
[1]-  Running                 gedit &
[2]+  Running                 emacs &
$ jobs -p
2581
2594
$ kill -2 `jobs -p`
$ jobs
[1]-  Interrupt               gedit
[2]+  Done                    emacs

答え4

だからそれも修正しました。 Bashでは、関数を定義し、それを使用して興味深い操作を実行できます。私と同僚はterminatorこれを一括実行に使用します。出力を表示するには、通常、複数のエミッタウィンドウを作成します(tmuxタブほどエレガントではありませんが、GUIに似たインターフェイスを使用できます。ハハ)。

私が思いついた設定と実行できる例は次のとおりです。

#!/bin/bash
custom()
{
    terun 'something cool' 'yes'
    run 'xclock'
    terun 'echoes' 'echo Hello; echo Goodbye; read'
    func()
    {
        local i=0
        while :
        do
            echo "Iter $i"
            let i+=1
            sleep 0.25
        done
    }
    export -f func
    terun 'custom func' 'func'
}

# [ Setup ]
run()
{
    "$@" &
    # Give process some time to start up so windows are sequential
    sleep 0.05
}
terun()
{
    run terminator -T "$1" -e "$2"
}
finish()
{
    procs="$(jobs -p)"
    echo "Kill: $procs"
    # Ignore process that are already dead
    kill $procs 2> /dev/null
}
trap 'finish' 2

custom

echo 'Press <Ctrl+C> to kill...'
wait

実行する

$ ./program_spawner.sh 
Press <Ctrl+C> to kill...
^CKill:  9835
9840
9844
9850
$ 

編集する @Maximさん、ちょうどあなたの提案を見て、はるかに簡単になりました!ありがとうございます!

関連情報