Open1

シェルスクリプトの多重起動防止について

epsilon phoenixepsilon phoenix

シェルスクリプトの多重起動防止について

概略

flockを用いない場合

プロセス名とスクリプトのフルパスをチェックして、重複実行を防止します。

  1. シェバン(Shebang)を忘れないこと。
    スクリプト名をpgrepで見つけられませんでした。
  2. cat /proc/$$/cmdline | xargs --null
    シェバンで指定したシェルとシェルスクリプト名が取得できます。
    シェバンを設定していないと、先頭に-がついたシェル名のみが取得されるケースがあります。
    またはシェル名のみが取得され、スクリプト名が含まれません。
    cmdlineはNULL文字で区切られているためxargsでNULLで分割することで、空白と置き換えています。
  3. pgrepでスクリプト名のみでプロセスIDを見つける
  4. 複数あったら、すでに起動されている可能性がある。
  5. スクリプトのフルパスとすでに起動しているスクリプトのフルパスが一致したら起動をやめる。

flockを用いる場合

実行しているスクリプトに自動でファイルディスクリプタを割り当てて、それがflockでブロックされるか否かを判定して、重複実行を防止します。

  1. ファイルディスクリプタ番号を取得する。
    {my_fd}という、使用するファイルディスクリプタ番号を自動で決めるための記法を活用する。
  2. 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が便利みたいです。

参考サイト

http://ogawa.s18.xrea.com/tdiary/20170529p01.html
https://www.mk-mode.com/blog/2016/02/21/linux-bash-check-double-start/