web-dev-qa-db-ja.com

シェルスクリプトで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
26
slipheed

killコマンドは逆です。

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

あなたが書くなら

kill -INT 0

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

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

kill 0 -INT

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

kill -TERM 0 -INT    

SIGTERM-INTに送信しようとすると、構文エラーが発生しますが、最初にSIGTERM0に送信し、それまで到達することはありません。)

したがって、メインスクリプトは、SIGTERMecho DONEを実行する前にwaitを取得しています。

追加

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
22
Mikel

残念ながら、バックグラウンドで開始されたコマンドは、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などの別の信号を使用します。

9

プロセスを強制終了してプロセスが終了するのを待つが、無期限ではない

信号タイプごとに最大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自体が一致しないようにします。

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

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

2
KrisWebDev

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

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

0
eacousineau