Open1
シェルスクリプトの多重起動防止について
シェルスクリプトの多重起動防止について
概略
flockを用いない場合
プロセス名とスクリプトのフルパスをチェックして、重複実行を防止します。
- シェバン(Shebang)を忘れないこと。
スクリプト名をpgrepで見つけられませんでした。 - cat /proc/$$/cmdline | xargs --null
シェバンで指定したシェルとシェルスクリプト名が取得できます。
シェバンを設定していないと、先頭に-がついたシェル名のみが取得されるケースがあります。
またはシェル名のみが取得され、スクリプト名が含まれません。
cmdlineはNULL文字で区切られているためxargsでNULLで分割することで、空白と置き換えています。 - pgrepでスクリプト名のみでプロセスIDを見つける
- 複数あったら、すでに起動されている可能性がある。
- スクリプトのフルパスとすでに起動しているスクリプトのフルパスが一致したら起動をやめる。
flockを用いる場合
実行しているスクリプトに自動でファイルディスクリプタを割り当てて、それがflockでブロックされるか否かを判定して、重複実行を防止します。
- ファイルディスクリプタ番号を取得する。
{my_fd}
という、使用するファイルディスクリプタ番号を自動で決めるための記法を活用する。 - flockで多重起動チェック
ファイルディスクリプタ番号をflock -n
コマンドでチェックして、ブロックされなかったら実行する。ブロックされたら終了する。
相対パスなどでの起動にも対応した多重起動防止
スクリプトのフルパスをチェックして、パスが一致するスクリプトが起動している場合に、多重起動を行わない。
test.sh
#!/bin/bash
#
#### 二重起動チェック
# 実行しているスクリプト名
SCRIPTS_NAME=$(basename $0)
# 実行しているスクリプトの相対パスに移動した上でpwdでパスを取得して、
# スクリプト名を加えて、フルパスにする。
FULLPATH=$(cd $(dirname $0) && pwd)/$SCRIPTS_NAME
# bash ./test.shなど、コマンドラインを取得する。(shebang) (script name)
CMDLINE=$(cat /proc/$$/cmdline | xargs --null)
# PIDを取得。同名のスクリプトも含めて探します。
pids=$(pgrep -f "$SCRIPTS_NAME")
if [[ "$$" != "$pids" ]]; then
for P in $pids
do
if [[ "$P" != "$$" ]]; then
# PIDが見つかったスクリプトのフルパスをlsofコマンドで見つけて、
# 実行しているスクリプトと同じかどうかをチェックします。
if [[ "$FULLPATH" == "$(lsof -p $P|awk '{print $NF}'|grep "$SCRIPTS_NAME")" ]];then
echo "Already running" >&2
exit 9
fi
fi
done
fi
# テスト用無限ループ
while true
do
sleep 3
done
flockを用いたら簡単でした。
ファイルのパス(相対パスや絶対パス)を意識しなくていい。
シェルの普通のコマンドでロックファイルを作成すると、kill -KILL
で終了した場合に、trapで削除することが困難そうである。
flockで制御すれば、それらを意識しなくて良いみたいです。
test.sh
#!/bin/bash
# 使用するファイルディスクリプタ番号を自動で決めるための記法
exec {my_fd}< "$0"
# flockで多重起動チェック
if ! flock -n ${my_fd};then
echo "Already running."
exit 1
fi
# テスト用無限ループ
while true
do
sleep 3
done
テスト
mkdir -p tmp
cd tmp
cp ../test.sh test.sh
chmod +x test.sh
./test.sh &
./test.sh
../tmp/test.sh
kill %
../tmp/test.sh &
./test.sh
../tmp/test.sh
関連情報
実行中シェルスクリプトのフルパス取得
シェバン(Shebang)が設定されていなくても取得できます。
fullpath=$(lsof -p $$|awk '{print $NF}'|grep "$(basename $0)")
shell_fullpath=$(cd $(dirname $0) && pwd)/$(basename $0)
実行中シェルのフルパス取得
shell_fullpath=$(readlink /proc/$$/exe)
親プロセスのIDを取得
cat /proc/$$/stat|cut -d" " -f4
備考
crontabに設定しているスクリプトが重くて処理が終わらず、重複して実行されること、あるいは複数のcrontabに登録されている同じスクリプトが同時に実行されることを防ぐのにflockが便利みたいです。
参考サイト