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


24

バックグラウンドプロセスを実行するようにシェルスクリプトを設定しようとしています。シェルスクリプトを実行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


トラップコマンドを共有しているおかげで、「シェルインターセプトキルスイッチ」を検索してこの質問を見つけました。GUIを閉じるボタンでアプリが正しく終了しない場合、Ctrl + Cでアプリを終了した後にコマンドを実行すると非常に便利です。
baptx

回答:


22

あなたのkillコマンドは後方です。

多くのUNIXコマンドと同様に、マイナスで始まるオプションは、他の引数の前に最初に来る必要があります。

書くなら

kill -INT 0

-INTオプションと見なし、(現在のプロセスグループ内のすべてのプロセスを意味する特別な番号です)に送信SIGINTします。00

しかし、あなたが書くなら

kill 0 -INT

を参照し0、これ以上オプションがないと判断するためSIGTERM、デフォルトで使用します。そして、それを現在のプロセスグループに送信ます。

kill -TERM 0 -INT    

(それはまた、送信しようとするだろうSIGTERM-INT構文エラーを引き起こすことになる、それが送信SIGTERMする0最初の、そして遠いことを取得することはありません。)

したがって、メインスクリプトはSIGTERMwaitおよびを実行する前にを取得しています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

おかげで、これは動作します!それは問題だったようです。
スリッピー

9

残念ながら、バックグラウンドで開始されたコマンドは、シェルによってSIGINTを無視するように設定されており、さらに悪いことに、を使用して無視しないことはできませんtrap。そうでなければ、あなたがしなければならないのはすべてです

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

プロセス1とプロセス2は端末のフォアグラウンドプロセスグループである同じプロセスグループの一部であるため、Ctrl-Cを押すとSIGINTを取得するためです。

上記のコードは、その点でPOSIX準拠ではないpdkshおよびzshで動作します。

他のシェルの場合、SIGINTのデフォルトハンドラーを復元するには、次のように別のものを使用する必要があります。

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

または、SIGTERMなどの別の信号を使用します。


2

プロセスを強制終了して、それが死ぬの待ちたいが、無期限ではない場合

信号タイプごとに最大60秒待機します。

警告:この回答は、killシグナルのトラップとディスパッチに関連するものではありません。

# close_app_sub GREP_STATEMENT SIGNAL DURATION_SEC
# GREP_STATEMENT must not match itself!
close_app_sub() {
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ ! -z "$APP_PID" ]; then
        echo "App is open. Trying to close app (SIGNAL $2). Max $3sec."
        kill $2 "$APP_PID"
        WAIT_LOOP=0
        while ps -p "$APP_PID" > /dev/null 2>&1; do
            sleep 1
            WAIT_LOOP=$((WAIT_LOOP+1))
            if [ "$WAIT_LOOP" = "$3" ]; then
                break
            fi
        done
    fi
    APP_PID=$(ps -x | grep "$1" | grep -oP '^\s*\K[0-9]+' --color=never)
    if [ -z "$APP_PID" ]; then return 0; else return "$APP_PID"; fi
}

close_app() {
    close_app_sub "$1" "-HUP" "60"
    close_app_sub "$1" "-TERM" "60"
    close_app_sub "$1" "-SIGINT" "60"
    close_app_sub "$1" "-KILL" "60"
    return $?
}

close_app "[f]irefox"

名前または引数で強制終了するアプリを選択します。grep自体と一致しないように、アプリ名の最初の文字の括弧を保持します。

いくつかの変更を加えるpidof process_nameと、psステートメントの代わりにPIDまたはより単純なものを直接使用できます。

コードの詳細:最後のgrepは、末尾のスペースなしでPIDを取得します。


1

バックグラウンドプロセスの管理が必要な場合、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

0

それで私もこれをいじくり回しました。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を編集して、あなたの提案を見ただけで、それは非常に簡単になります!ありがとう!

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