シェルスクリプトの二重起動禁止

検証環境

Fedora release 8 (Werewolf)
Bash GNU bash, version 3.2.33(1)-release (i386-redhat-linux-gnu)



プロセスを見る方法

[ $$ != `pgrep -fo $0` ] && { echo 'Cannot run multiple instance.' >&2; exit 9; }
$$ 自分自身のプロセスID
pgrep パターンにマッチするプロセスのプロセスIDを返す
-f パターンをコマンドライン全体(パスや引数を含める)と照合する。デフォルトではプロセス名のみと照合する
-o パターンにマッチする一番古いプロセスのプロセスIDを返す
$0 自分自身のプロセス名(パス名)
>&2 標準エラー出力にリダイレクトする


失敗例「cronで実行すると常に二重起動と判定される」

この方法により二重起動をチェックするシェルスクリプトを作成し、cronで実行したところ、常に二重起動と判定されて処理が中断する、というハメに陥りました。もちろん、二重起動は誤判定です。

プロセスIDを調べたところ、

echo $$              # => 6943
echo `pgrep -fo $0`  # => 6942

と、自分自身のプロセスIDパターンにマッチする一番古いプロセスのプロセスIDが一致していません。

それぞれのプロセスIDに対応するプロセスを調べると、

6942 ? Ss 0:00 /bin/sh -c /home/tetsuyai/bin/create_summaries.bash 2>> /home/tetsuyai/create_summaries.error.log
6943 ? S 0:00 /bin/bash /home/tetsuyai/bin/create_summaries.bash

となっていました。6942はcronによって実行されたプロセス、6943はそこから実行された子プロセスです。

本命の処理は6943で実行されています。二重起動のチェックもそこで行われるので、自分自身のプロセスIDは6943になります。一方、pgrepに-fオプションを渡しているため、/home/tetsuyai/bin/create_summaries.bashにマッチする一番古いプロセスは親プロセスである6942になります。これが誤判定の原因でした。

ひとつの解決策として、cronで実行するときはpgrepに-fオプションを渡さない、という方法があります。その場合、pgrepはプロセス名のみと照合するため、6942はsh、6943はcreate_summaries.bashとなり、`basename $0`で取り出したファイル名とマッチするのは6943になります。

_pname=`basename $0`
[ $$ != `pgrep -fo $_pname` ] && { echo 'Cannot run multiple instance.' >&2; exit 9; }

この方法は比較的簡単に誤判定を回避できますが、パスも引数も無視してプロセス名でのみ比較するため、それはそれで都合が悪い、というケースもあるでしょう。その場合、次の「ロックファイルを使う方法」が適しているかもしれません。



ロックファイルを使う方法

_lockfile="/tmp/`basename $0`.lock"
ln -s /dummy $_lockfile 2> /dev/null || { echo 'Cannot run multiple instance.' >&2; exit 9; }
trap "rm $_lockfile; exit" 1 2 3 15

# Should write your process.

rm $_lockfile
basename パス名からファイル名のみを返す
/dummy シンボリックリンクの仮の参照先となる、実在しないエントリ
2> /dev/null エラーメッセージを出力しない
trap 送出されたシグナルを補足し、あらかじめ指定された処理を実行する
1 2 3 15 補足するシグナルの種類。1はHUP(再起動)、2はINT(割り込み)、3はQUIT(終了)、15はTERM(終了)