前書き :同じタイミングで動かれると困る

同じシェルスクリプトが二重起動すると、処理によっては「無駄で時間のかかる処理を二重に行い、システムリソースを消費する事」があります。

例えば、cron/rsyncコマンドを組み合わせてローカルファイル(音楽、動画など)をリモートサーバへ定期バックアップしている場合、一つ前の定期バックアップが終わる前に、次の定期バックアップが開始される可能性があります。タイミング次第では、同じファイルのバックアップを試み、ネットワークやI/Oリソースを無駄に消費してしまいます。

そこで、本記事ではシェルスクリプトの起動時に、同じスクリプトが実行中かどうかを確認する方法を紹介します。

プロセスIDとスクリプト名を用いて二重起動を防止

二重起動を防止するには、以下の処理を行います。

  • シェルスクリプトのプロセスIDを取得
  • 現在実行中のプロセス一覧に対して、同名のシェルスクリプトが存在するかをpgrepコマンドで検索

実装例は、以下の通りです。

function IsRunning() {
    if [ $$ -ne $(pgrep -fo "$0") ]; then
        echo "起動済みです。"
        exit 1
    fi
}

プロセスIDはシェルの特殊変数$$で取得でき、シェルスクリプト名も特殊変数$0で取得できます。

pgrepコマンドはプロセス一覧を検索し、検索にヒットしたプロセスIDを返します。上記の実装例で使用しているオプションの意味は、

  • fオプション:検索対象をフルPATHのプロセス名に変更
  • oオプション:検索にヒットしたプロセスの中から最も古いプロセスIDのみを返すように変更

です。

fオプションは検索マッチ数を増やすため(シェルスクリプト名を確実に検索ヒットさせるため)に付与し、oオプションはpgrepコマンド結果をプロセスID1個分とするために付与しています。

pgrepコマンドが複数のプロセスIDを返した場合は以下のような結果となるため、シェルスクリプトの二重起動を検出できていても"if [ $$ -ne $(pgrep -fo “$0”) ];“部分の判定が正しく動きません。

$ pgrep -f bash                                                                                                                                                                                                   11153
13960
16191

実行例(検証)

検証用シェルスクリプトとして、only.shスクリプトを用意します。only.shスクリプトは、同名シェルスクリプトが実行済みかをチェックした後に無限ループを行います。

#!/bin/bash

function IsRunning() {
    if [ $$ -ne $(pgrep -fo "$0") ]; then
        echo "起動済みです。"
        exit 1
    fi
}

# 同名シェルスクリプトの実行チェック
IsRunning

# 無限ループ
while true
do
    :
done

検証手順として、

  1. only.shスクリプトをバックグラウンド実行(&を付けて実行)
  2. only.shスクリプトをファオアグラウンド実行

を順に行います。

バックグラウンド実行中のonly.shスクリプトは無限ループ中なので、killしない限り実行を継続しています。その状態でonly.shスクリプトを再実行すれば、同名シェルスクリプトの二重起動を検出できます。

以下、実行例です。

(注釈):バックグラウンド実行
$ ./only.sh &
[1] 16065

(注釈):フォアグラウンド実行
$ ./only.sh 
起動済みです。      (注釈) 二重起動が防止できている。

(注釈かつ補足):pgrepコマンドは、以下のような結果を返している。
$ pgrep -fo "only.sh"
16065

二回目のonly.shスクリプトの実行(フォアグラウンド実行)において、二重起動防止ができている事が確認できました。