シェルスクリプトで実行中のプロセスの終了を待機する方法(waitコマンド以外)

2019-09-02Bash

はじめに

シェルスクリプトで実行中のプロセスが終了を待機する方法について取り上げます。

簡単な方法として、 wait コマンドを利用する方法がありますが wait コマンドでは対応できないケースへの対処法についても取り上げます。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ bash -version | head -n 1
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)

wait コマンドを利用する方法

まずは実行中のプロセスを作成

実行中のプロセスを以下のコマンドで作成します。

$ bash -c '
for i in {0..19}; do
  echo "execute($i)."
  sleep 1
done
' > log.txt &

急いで標準出力をリダイレクトした log.txt の内容を確認します。

$ tail -f log.txt
execute(0).
execute(1).
execute(2).
execute(3).
execute(4).
execute(5).
execute(6).
execute(7).
execute(8).
execute(9).
execute(10).
execute(11).
execute(12).
execute(13).
execute(14).
execute(15).
execute(16).
execute(17).
execute(18).
execute(19).

20回出力されて処理が終了します。

では、今度はプロセスをバックグラウンドで立ち上げた後、 wait します。

# 先程のコマンドを立ち上げて
$ bash -c 'for i in {0..9}; do echo "execute($i)."; sleep 1; done' > log.txt &

# 終了を待つと、約10秒後に "finish!" が表示させる
$ wait && echo finish!
[1]+  Done                    bash -c 'for i in {0..9}; do echo "execute($i)."; sleep 1; done' > log.txt
finish!

wait コマンドの課題

wait コマンドには制限があります。
待ち受ける対象のコマンドは、 wait コマンドを実行するシェルの子プロセスでなければいけません。

今回のケースでは以下のようになっているため、 wait が可能です。

* ログインシェル( `bash` )
    * `bash -c 'for i in {0..9}; do echo "execute($i)."; sleep 1; done' > log.txt &`
    * `wait`

別途起動したログインシェルや cron のようなプロセスからは終了を監視し待機できません。

kill コマンドを利用する

kill コマンドを利用する方法があります。

kill コマンドに -0 というオプションがあります。

$ man 2 kill
...
If sig is 0, then no signal is sent, but error checking is still performed; 
this can be used to check for the existence of a process ID or process 
group ID.
...

kill -0 コマンドは、停止シグナルを送信しません。
ただし、引数で渡されたプロセスIDに該当するプロセスが終了した場合には、エラーステータスを返却します。
これを用いてプロセスの終了監視をすることができます。

kill コマンドを利用したプロセスの終了監視サンプル

早速試してみます。
監視対象のプロセスを以下のコマンドを使って起動します。

先程のコマンドに少しだけ手を加えました。
/tmp/pid に実行中のプロセスのPIDを書き込んでいます。

$ bash -c '
echo $$ >/tmp/pid
for i in {0..19}; do
  echo "execute($i)."
  sleep 1
done
'

上記のコマンドを実行中に別のターミナルを立ち上げ、以下の監視コマンドを実行します。
先程プロセスIDを書き込んだファイルを読み込み、 kill コマンドの引数として渡しています。

$ while kill -0 $(cat /tmp/pid); do
  echo check.
  sleep 1
done \
  && echo "finish."

check.
check.
check.
check.
check.
check.
check.
bash: kill: (410) - No such process
finish.

先の監視対象プロセスの終了と同時に、監視プロセスも終了したと思います。
エラーメッセージが気持ち悪い場合には以下のように標準エラー出力を /dev/null しましょう。

$ while kill -0 $(cat /tmp/pid) 2>/dev/null; do
  echo check.
  sleep 1
done \
  && echo "finish."

監視機関にタイムアウトを設定する

監視プロセス側でも、「いつまでも待っていられない」というケースも有るかと思います。
その場合には、 timeout コマンドを利用してやることができます。

以下の例は先ほどと同じコマンドを実行して監視を行いますが、 5秒 を過ぎたた処理を停止します。

timeout コマンドの第一引数には「制限時間(秒)」を、第二引数以降には「コマンド」を指定します。

$ timeout 5 bash -c '
while kill -0 $(cat /tmp/pid) 2>/dev/null; do
  echo check.
  sleep 1
done \
  && echo "finish."
'
check.
check.
check.
check.
check.

ひとこと

pspgrep を使う方法もありますが、こちらの方法が /etc ディレクトリ配下のシェルスクリプトに書かれていることが多いため紹介しました。

2019-09-02Bash