シェルスクリプトでロックファイルを使った排他制御をする場合、普通にファイルだとうまく行かない場合がある。なぜならロックファイルの作成と確認がアトミックに行われないから。
でこれを回避する方法として、シンボリックリンクを使う方法が紹介されているけど、他にも色々あるみたいなので試してみた。
シンボリックリンク
とりあえず上記でも紹介されているシンボリックリンクを使うやり方。
#!/bin/bash LOCK_FILE=./file.lock ## ロックファイルの確認と作成 if ! ln -s $$ $LOCK_FILE; then echo "LOCKED" exit 0 fi ## メインの処理 for ((i=0;$i<10;i=$i+1)); do echo $i sleep 1 done echo OK ## ロックファイルの削除 rm -f $LOCK_FILE exit 0
2つのターミナルで実行するとこうなる。
$ ./lock.sh 0 1 2 ...(snip)... 7 8 9 OK
$ ./lock.sh ln: ./file.lock: File exists LOCKED
実行中は以下の通りシンボリックリンクが張られている。実際には実体が存在しないリンクになってるけど、問題ない。
$ ls -lt lrwxr-xr-x 1 xxxxx xxxxx 5 7 6 02:16 file.lock -> 28128
ディレクトリを使う
使い方としてはどうよ?と思わないでもないけど、ディレクトリでもよい。個人的には昔はこれをよく使っていた。
#!/bin/bash LOCK_DIR=./lock ## ロックファイルの確認と作成 if ! mkdir $LOCK_DIR; then echo "LOCKED" exit 0 fi ## メインの処理 for ((i=0;$i<10;i=$i+1)); do echo $i sleep 1 done echo OK ## ロックファイルの削除 rmdir $LOCK_DIR exit 0
実行結果
$ ./lock.sh 0 1 2 ...(snip)... 7 8 9 OK
$ ./lock.sh mkdir: ./lock: File exists LOCKED
lockfileコマンド
procmailに含まれているlockfileコマンドを使う。個人的にこれは知らなかった。
Macの場合はhomebrewのprocmailパッケージに含まれている。
$ brew install procmail
$ which lockfile
/usr/local/bin/lockfile
スクリプトはこんな感じ。
#!/bin/bash LOCK_FILE=./file.lock ## ロックファイルの確認と作成 if ! lockfile $LOCK_FILE; then echo "LOCKED" exit 0 fi ## メインの処理 for ((i=0;$i<10;i=$i+1)); do echo $i sleep 1 done echo OK ## ロックファイルの削除 rm -f $LOCK_FILE exit 0
これの面白いところは、オプションを指定せずに実行すると、ロックファイルが作れるようになるまで待つこと。
$ ./lock.sh 0 1 2 ...(snip)... 7 8 9 OK
$ ./lock.sh (1つ目の実行が終わるまでは停止したまま、1つ目の実行が終わると処理が進む) 0 1 2 ...(snip)...
オプションでは、
- 何秒間隔で何回リトライする
- ロックファイルのタイムスタンプから何秒経っていたら強制的に削除する
というようなことができて、例えば以下のように指定すると、1秒間隔で2回リトライになる。
if ! lockfile -1 -r 2 $LOCK_FILE; then
ロック中はこんな感じになる。
$ ./lock.sh lockfile: Sorry, giving up on "./file.lock" LOCKED
ロックファイルが一定時間よりも古ければ強制的に削除する、というのは、エラーなどでロックが作られっぱなしになるようなケースを踏まえると役に立つかもしれない。
manはこちら
まとめ
他にflock使う方法もある。個人的には、ロックを作成する場合はエラー処理をきちんとやらないと、ロックされっぱなしになると思うのでオプションも含めてlockfileコマンド、うまく使えたら良さげと思った。