【Bash】同じシェルスクリプトの二重起動(並列実行)を防止する方法

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

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

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

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

                     

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

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

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

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

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

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

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

です。

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

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

                      

実行例(検証)

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

検証手順として、

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

を順に行います。

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

以下、実行例です。

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

                               

おすすめ

4件のフィードバック

  1. aaa より:

    ちょっと質問です。cron実行時に多重起動チェックをしたいと思っています。
    かんたんで良いのでヒントをいただけないでしょうか。
    まだ初心者なので理由はよく分からないのですが、cronで実行した場合にうまくチェックが働きません。
    ついでに、cron実行と手動実行が混在する場合のやりかたについても、ヒントをいただけないでしょうか。

    • nao より:

      >かんたんで良いのでヒントをいただけないでしょうか。
      では、簡単にヒントを書きます。
         
      >cronで実行した場合にうまくチェックが働きません。
      本記事で紹介した方法を改良しないと動作しません。
      cronによるシェルスクリプト(例:/home/lemmy/test/only.sh)実行時は、以下のように二つのプロセスが動きます。
        
      lemmy 24630 0.0 0.0 2036 512 ? Ss 21:59 0:00 /bin/sh -c /home/lemmy/test/only.sh
      lemmy 24631 0.0 0.0 6456 2900 ? S 21:59 0:00 /bin/bash /home/lemmy/test/only.sh
        
      cronは、実行時に”/bin/sh -c $(実行したいスクリプト)”という形式で処理を行うようです。
      この形式で処理すると、子プロセス(PID=24631の方)が生成され、親プロセスと子プロセスで同じスクリプト名を持ちます。
      (cronを使わず、/bin/sh -c $(実行したいスクリプト)と入力しても、二重起動が正しく判定できない筈です)
        
      本記事のIsRunning()は、既に親プロセスがonly.shを動かしていると誤検知します。
      実際は、親プロセスが子プロセスを生成しただけで、only.shは動いていません。
        
      この現象を回避するには、
       ① 同名スクリプトのPIDを全て収集
         かつ
       ② “/bin/sh -c”を含むPIDは除外
      という対応が必要です。
         
      ①はpgrep -f $(スクリプト名)で収集できます。
      ②はps -x | egrep $(①の結果) | grep -v “/bin/sh -cあたりで対応できます(このワンライナーは少し改良しないと駄目)
        
        
      > ついでに、cron実行と手動実行が混在する場合のやりかたについても、ヒントをいただけないでしょうか。
      cronが対応できていれば、混在していても問題ない筈です。

  2. aaa より:

    縦読みでした。すみません。

    • nao より:

      こんな内容がシッカリした質問で、縦読みなんて気づけません……
      (本名公開しているので、縦読み質問は残します。内容的には、良い気付きになったので)