web-dev-qa-db-ja.com

2つのバックグラウンドコマンドが与えられ、どちらかが終了したら残りのコマンドを終了します

2つのサーバーを起動する単純なbashスクリプトがあります。

#!/bin/bash
(cd ./frontend && gulp serve) & (cd ./backend && gulp serve --verbose)

2番目のコマンドが存在する場合、最初のコマンドは実行を続けているようです。

これをどのように変更すれば、どちらかのコマンドが終了した場合、もう一方のコマンドは終了しますか?

バックグラウンドプロセスのエラーレベルを確認する必要はありません。バックグラウンドプロセスが終了したかどうかを確認するだけです。

14
blah238

これは両方のプロセスを開始し、最初のプロセスが完了するのを待ってから、もう一方を強制終了します。

#!/bin/bash
{ cd ./frontend && gulp serve; } &
{ cd ./backend && gulp serve --verbose; } &
wait -n
pkill -P $$

使い方

  1. 開始:

    { cd ./frontend && gulp serve; } &
    { cd ./backend && gulp serve --verbose; } &
    

    上記の2つのコマンドは、両方のプロセスをバックグラウンドで開始します。

  2. 待つ

    wait -n
    

    これは、いずれかのバックグラウンドジョブが終了するのを待ちます。

    -nオプションのため、これにはbash 4.3以降が必要です。

  3. Kill

    pkill -P $$
    

    これにより、現在のプロセスが親であるすべてのジョブが強制終了されます。つまり、これはまだ実行中のバックグラウンドプロセスをすべて強制終了します。

    システムにpkillがない場合は、この行を次のように置き換えてみてください:

    kill 0
    

    これも 現在のプロセスグループを殺す です。

簡単にテスト可能な例

スクリプトを変更することで、gulpがインストールされていなくてもテストできます。

$ cat script.sh 
#!/bin/bash
{ sleep $1; echo one;  } &
{ sleep $2; echo two;  } &
wait -n
pkill -P $$
echo done

上記のスクリプトはbash script.sh 1 3として実行でき、最初のプロセスが最初に終了します。または、bash script.sh 3 1として実行すると、2番目のプロセスが最初に終了します。どちらの場合でも、これは期待どおりに動作することがわかります。

22
John1024

私のシステム(Centos)では、wait-nがないため、次のようにしました。

{ sleep 3; echo one;  } &
FOO=$!
{ sleep 6; echo two;  } &
wait $FOO
pkill -P $$

これは「どちらか」を待つのではなく、最初のものを待ちます。ただし、どのサーバーが最初に停止するかがわかっている場合は、それでも役立ちます。

1
Ondra Žižka

これはトリッキーです。これが私が考案したものです。それを簡素化/合理化することが可能かもしれません:

#!/bin/sh

pid1file=$(mktemp)
pid2file=$(mktemp)
stat1file=$(mktemp)
stat2file=$(mktemp)

while true; do sleep 42; done &
main_sleeper=$!

(cd frontend && gulp serve           & echo "$!" > "$pid1file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat1file"; kill "$main_sleeper" 2> /dev/null) &
(cd backend  && gulp serve --verbose & echo "$!" > "$pid2file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat2file"; kill "$main_sleeper" 2> /dev/null) &
sleep 1
wait "$main_sleeper" 2> /dev/null

if stat1=$(<"$stat1file")  &&  [ "$stat1" != "" ]  &&  [ "$stat1" != 0 ]
then
        echo "First process failed ..."
        if pid2=$(<"$pid2file")  &&  [ "$pid2" != "" ]
        then
                echo "... killing second process."
                kill "$pid2" 2> /dev/null
        fi
fi
if [ "$stat1" = "" ]  &&  \
   stat2=$(<"$stat2file")  &&  [ "$stat2" != "" ]  &&  [ "$stat2" != 0 ]
then
        echo "Second process failed ..."
        if pid1=$(<"$pid1file")  &&  [ "$pid1" != "" ]
        then
                echo "... killing first process."
                kill "$pid1" 2> /dev/null
        fi
fi

wait
if stat1=$(<"$stat1file")
then
        echo "Process 1 terminated with status $stat1."
else
        echo "Problem getting status of process 1."
fi
if stat2=$(<"$stat2file")
then
        echo "Process 2 terminated with status $stat2."
else
        echo "Problem getting status of process 2."
fi
  • まず、プロセスを開始します(while true; do sleep 42; done &)永遠にスリープ/一時停止します。 2つのコマンドが特定の時間(たとえば、1時間)以内に終了することが確実な場合は、これを、それを超える単一のスリープ(たとえば、sleep 3600)。次に、次のロジックを変更して、これをタイムアウトとして使用できます。つまり、その時間が経過してもプロセスがまだ実行されている場合は、プロセスを強制終了します。 (上記のスクリプトは現在それを行わないことに注意してください。)
  • 2つの非同期(並行バックグラウンド)プロセスを開始します。
    • 必要ありません./ for cd
    • command & echo "$!" > somewhere; wait "$!"は、プロセスを非同期で開始し、そのPIDを取得して、それを待機するトリッキーな構造です。フォアグラウンド(同期)プロセスのようなものにします。しかし、これは(…)リスト全体がバックグラウンドにあるため、gulpプロセスは非同期で実行されます。
    • gulpプロセスのいずれかが終了したら、そのステータスを一時ファイルに書き込み、「永久スリープ」プロセスを終了します。
  • sleep 1最初のバックグラウンドプロセスが停止してから2番目のプロセスがPIDをファイルに書き込む機会が得られるという競合状態から保護します。
  • 「永久スリープ」プロセスが終了するまで待ちます。これは、上記のように、gulpプロセスのどちらかが終了した後に発生します。
  • 終了したバックグラウンドプロセスを確認します。失敗した場合は、もう一方を殺してください。
  • 1つのプロセスが失敗してもう1つのプロセスが終了した場合は、2番目のプロセスが終了してステータスをファイルに保存するまで待ちます。最初のプロセスが正常に終了した場合は、2番目のプロセスが終了するまで待ちます。
  • 2つのプロセスのステータスを確認します。

完全を期すために、ここで私が最終的に使用したものを示します。

#!/bin/bash
(cd frontend && gulp serve) &
(cd backend && gulp serve --verbose) &
wait -n
kill 0

これは、Git for Windows 2.5.3 64ビットで動作します。古いバージョンはwait-nオプションを受け入れない場合があります。

1
blah238