シェルスクリプトが二重起動されないように flock で排他制御を行う

Bash

はじめに

最近ではすっかり Docker を使うようになり、 Linux サーバの Daemon に触れることがなくなりました。

久しぶりに Daemon 設定を触る機会がありました。

起動スクリプトにシェルスクリプトを設定しましたが、Daemon 起動中に万が一に二重起動してしまったら、あるいは手動実行されてしまったらといった場合に備えて、二重起動を抑制する仕組みを入れておきたいと思いました。

シェルスクリプトが二重起動されないように方法について紹介します。

検証環境

$ uname -moi
x86_64 x86_64 GNU/Linux

$ head -n 2 /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"

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

$ flock --version
flock from util-linux 2.23.2

flock コマンドを利用する

Linux に用意されている flock コマンドを使うことで二重起動を抑制することができます。

$ type flock
flock is hashed (/usr/bin/flock)

flockファイルディスクリプタ を指定することで、対象ファイルをロックするコマンドです。

( ファイルディスクリプタ とは、「ファイル」を操作するための別名、番号です。 )

過去に少しだけ「ファイルディスクリプタ」に触れた記事もありますので、参考にどうぞ。

サンプルスクリプト

二重起動を抑制するためのサンプルスクリプトを掲載します。

以下のファイルを test.sh という名前で作成し、保存しておきます。

#!/usr/bin/env bash
set -o errexit
set -o nounset

# 任意のロックファイル名を指定
# (ここではスクリプト名がmain.shの場合、main.lockとなるように命名)
LOCK_FILE="${0%.*}.lock"

# 0/1/2以外の任意のファイルディスクリプタ(番号)を指定
exec 200>"${LOCK_FILE}"

flock -n 200 \
  || {
    # 任意の終了コードを返却
    echo "The script is already running."
    exit 100
  }

### メイン処理開始
echo "PID=$$ - start"
sleep 3
echo "PID=$$ - end"
echo

サンプルスクリプトの解説

  • 1〜3 行目 : はおまじないです。なくても動作します。
    • ShellbangBash のオプション についてはここでは割愛します。
  • 7 行目 : LOCK_FILE 変数に排他制御に利用するロックファイル名を格納しています。
    • 動的にファイル名を算出していますが、今回のスクリプト名 test.sh の拡張子部分を .lock に変更した test.lock というファイル名を算出しています。
  • 10 行目 : $LOCK_FILE ( = test.lock ) ファイルに対して、 200 という番号のファイルディスクリプタを割り当てています。
  • 12 行目 : ファイルディスクリプタ 200 のロックを取得します。 ( 0/1/2 は予約されたファイルディスクリプタ番号ですので、3 以上であれば何でも構いません。 )

12 行目で初めて flock が登場します。
flock でファイルディスクリプタ 200 のロックを取得しようとします。

flock -n とすると、すでに別のプロセスが flock にてロックを取得していた場合にエラー停止してくれます。 -n オプションを付与しなかった場合は、 ロック開放まで待ちます

-n オプションの有無は実現したい要件次第で使い分けると良いでしょう。

ひとこと

pid ファイルを作成してロックする方法を見たことがありましたが、 ちょっと怪しかった。

flock の方法、4 並列で 1 時間動かし続けてみましたが二重起動なく稼働してくれました。

「ファイルの有無」では排他は難しいですよね。「ファイルへのチャネル」をオープンできるかが挙動として確実そうです。

Bash