はじめに
以前、シェルスクリプトでAtomicなファイル操作をするでロックファイルをAtomicに作成する方法を書いた。
ロックファイルを使ってスクリプト実行するプロセスを一意にしぼる方法は他にもあるため、どの方法がベストか検討してみる。
ロックファイルをスクリプト内で作成する方法
シェルスクリプトでAtomicなファイル操作をするで記載した方法で、スクリプト内でロックファイルを自分で作る。
if文の中がメインの処理で、この部分で重複起動が起きないようにする。
echo 'start'
start=$(date "+%s")
ln -s $$ file.lock 2>/dev/null
lock=$?
if [ $lock -eq 0 ]; then
echo 'script in lock start'
sleep 5
echo 'script in lock end'
unlink file.lock 2>/dev/null
fi
echo "result is $lock"
echo 'end'
end=$(date "+%s")
echo "elapse is $((end - start))"
このスクリプトを二つのコンソールから同時に実行すると、それぞれ以下の出力になる。
# 1つ目
start
script in lock start
script in lock end
result is 0
end
elapse is 5
# 2つ目
start
result is 1
end
elapse is 0
うまく動いているが、スクリプトの実行途中にCtrl + c
で処理を終了させるとロックファイルがごみとして残ってしまう。対策としてはtrap
を使ってCtrl + c
等が打たれても後処理をできるようにすることがある。
echo 'start'
start=$(date "+%s")
trap "unlink file.lock;exit" 2 15
ln -s $$ file.lock 2>/dev/null
lock=$?
if [ $lock -eq 0 ]; then
echo 'script in lock start'
sleep 5
echo 'script in lock end'
unlink file.lock 2>/dev/null
fi
echo "result is $lock"
echo 'end'
end=$(date "+%s")
echo "elapse is $((end - start))"
しかしkill -9
でプロセスを殺された場合はtrap
されないので、完全に欠点を解消できているわけではない。
flockを使う方法
flock
コマンドを使うと、ファイルをロックしてくれる。
manで使用法を確認すると、共有ロック/排他ロックやタイムアウト時間を指定できる。ロックファイルあるいはロックディレクトリが指定できるほかにファイルディスクリプタもロック対象にすることができる。flockがロックを獲得できなかった場合は、終了ステータスが1
になる。
flock [-sxon] [-w timeout] lockfile [-c] command...
flock [-sxon] [-w timeout] lockdir [-c] command...
flock [-sxun] [-w timeout] fd
flockをどのように使うかという点では、ロジック本体スクリプトと排他制御スクリプトを分けて、排他制御スクリプトからロジック本体スクリプトを呼ぶのが一般的かと思う。
main.sh
flock -xn file.lock -c 'bash logic.sh'
しかしファイルを分けてしまうと直接ロジック本体スクリプトを実行してしまうおそれもある。排他制御とロジック本体を一緒にするためにヒアドキュメントを使ってみた。
echo 'start'
start=$(date "+%s")
cat << 'SCRIPT' | flock -xn $0 -c 'bash -'
echo 'script in lock start'
sleep 5
echo 'script in lock end'
SCRIPT
echo "result is $?"
echo 'end'
end=$(date "+%s")
echo "elapse is $((end - start))"
またロックファイルとして使っていたfile.lock
をスクリプト自身($0
)に変更している。スクリプト実行中にスクリプトファイルを編集することがないのであれば、スクリプト自身をロックファイルにすることで余計なロックファイルが作られないので、ディレクトリがきれいになる。