ウェブサイト検索

Bash スクリプトの実行を一度に 1 回に制限することで、これらの問題を回避します


重要なポイント

  • 同時実行の問題を防ぐために、pgrep、lsof、または flock を使用してスクリプトのインスタンスが 1 つだけ実行されていることを確認してください。
  • 他の実行中のインスタンスが検出された場合にスクリプトを自己終了するチェックを簡単に実装できます。
  • exec コマンドと env コマンドを活用することで、flock コマンドはこれらすべてを 1 行のコードで実現できます。

一部の Linux スクリプトにはこのような実行オーバーヘッドがあるため、複数のインスタンスを同時に実行しないようにする必要があります。ありがたいことに、独自の Bash スクリプトでこれを実現する方法がいくつかあります。

一度で十分な場合もあります

一部のスクリプトは、そのスクリプトの前のインスタンスがまだ実行中の場合は起動すべきではありません。スクリプトが過剰な CPU 時間と RAM を消費する場合、または大量のネットワーク帯域幅やディスク スラッシングを生成する場合、スクリプトの実行を一度に 1 つのインスタンスに制限するのが常識です。

しかし、単独で実行する必要があるのはリソースを大量に消費するだけではありません。スクリプトがファイルを変更する場合、ファイルへのアクセスをめぐってスクリプトの 2 つ (またはそれ以上) のインスタンス間で競合が発生する可能性があります。更新が失われるか、ファイルが破損する可能性があります。

これらの問題のシナリオを回避する 1 つの手法は、スクリプト自体の他のバージョンが実行されていないことをスクリプトにチェックさせることです。他に実行中のコピーが検出されると、スクリプトは自己終了します。

もう 1 つの手法は、スクリプトの起動時にスクリプト自体をロックダウンして、他のコピーが実行されないようにスクリプトを設計することです。

最初の手法の 2 つの例を見てから、2 番目の手法を実行する 1 つの方法を見ていきます。

pgrep を使用して同時実行を防止する

pgrep コマンドは、Linux コンピューター上で実行されているプロセスを検索し、検索パターンに一致するプロセスのプロセス ID を返します。

「loop.sh」というスクリプトがあります。これには、ループの反復を出力してから 1 秒間スリープする for ループが含まれています。これを10回繰り返します。

#!/bin/bash
for (( i=1; i<=10; i+=1 ))
do
 echo "Loop:" $i
 sleep 1
done
exit 0

2 つのインスタンスを実行するように設定し、pgrep を使用して名前で検索しました。

pgrep loop.sh

2 つのインスタンスを見つけて、そのプロセス ID を報告します。 -c (カウント) オプションを追加すると、pgrep がインスタンスの数を返すようになります。

pregp -c loop.sh

このインスタンスの数をスクリプトで使用できます。 pgrep によって返された値が 1 より大きい場合は、複数のインスタンスが実行されている必要があり、スクリプトは終了します。

この手法を使用するスクリプトを作成します。これを pgrep-solo.sh と呼びます。

if 比較は、pgrep によって返された数値が 1 より大きいかどうかをテストします。存在する場合、スクリプトは終了します。

# count the instances of this script 
if [ $(pgrep -c pgrep-solo.sh) -gt 1 ]; then
 echo "Another instance of $0 is running. Stopping."
 exit 1
fi

pgrep によって返された数値が 1 の場合、スクリプトは続行できます。完全なスクリプトは次のとおりです。

#!/bin/bash
echo "Starting."
# count the instances of this script 
if [ $(pgrep -c pgrep-solo.sh) -gt 1 ]; then
 echo "Another instance of $0 is running. Stopping."
 exit 1
fi
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
 echo "Loop:" $i
 sleep 1
done
exit 0

これをお気に入りのエディタにコピーし、pgrep-solo.sh として保存します。次に、chmod で実行可能にします。

chmod +x pgrep-loop.sh

実行するとこんな感じになります。

./pgrep-solo.sh

しかし、別の端末ウィンドウですでに実行されている別のコピーを使用して起動しようとすると、それが検出され、終了します。

./pgrep-solo.sh

lsof を使用して同時実行を防止する

lsof コマンドでも同様のことができます。

-t (簡潔) オプションを追加すると、lsof はプロセス ID をリストします。

lsof -t loop.sh

lsof からの出力を wc にパイプできます。 -l (行) オプションは行数をカウントします。このシナリオでは、行数はプロセス ID の数と同じになります。

lsof -t loop.sh | wc -l

これをスクリプトの if 比較のテストの基礎として使用できます。

このバージョンを lsof-solo.sh として保存します。

#!/bin/bash
echo "Starting."
# count the instances of this script 
if [ $(lsof -t "$0" | wc -l) -gt 1 ]; then
 echo "Another instance of $0 is running. Stopping."
 exit 1
fi
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
 echo "Loop:" $i
 sleep 1
done
exit 0

chmodを使用して実行可能にします。

chmod +x lsof-solo.sh

ここで、lsof-solo.sh スクリプトが別のターミナル ウィンドウで実行されているため、2 番目のコピーを開始できません。

./lsof-solo.sh

pgrep メソッドでは外部プログラム (pgrep) への呼び出しが 1 回だけ必要ですが、lsof メソッドでは 2 回 (lsof と wc) が必要です。ただし、lsof メソッドが pgrep メソッドよりも優れている点は、if 比較で $0 変数を使用できることです。これはスクリプト名を保持します。

つまり、スクリプトの名前を変更しても引き続き機能します。 if 比較行を編集してスクリプトの新しい名前を挿入することを忘れる必要はありません。 $0 変数にはスクリプト名の先頭に「./」が含まれています (./lsof-solo.sh など)。pgrep はこれを好みません。

flock を使用して同時実行を防ぐ

3 番目の手法では、スクリプト内からファイルとディレクトリのロックを設定するように設計された flock コマンドを使用します。ロックされている間は、他のプロセスはロックされたリソースにアクセスできません。

この方法では、スクリプトの先頭に 1 行を追加する必要があります。

[ "${GEEKLOCK}" != "$0" ] && exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@" || :

これらの象形文字をすぐに解読します。とりあえず、動作することを確認してみましょう。これを flock-solo.sh として保存します。

#!/bin/bash
[ "${GEEKLOCK}" != "$0" ] && exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@" || :
echo "Starting."
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
 echo "Loop:" $i
 sleep 1
done
exit 0

もちろん、実行可能にする必要があります。

chmod +x flock-solo.sh

あるターミナル ウィンドウでスクリプトを開始し、別のターミナル ウィンドウで再度実行しようとしました。

./flock-solo
./flock-solo
./flock-solo

他のターミナル ウィンドウのインスタンスが完了するまで、スクリプトを起動できません。

魔法を実行する行を選択してみましょう。その中心となるのがflockコマンドです。

flock -en "$0" "$0" "$@"

flock コマンドは、ファイルまたはディレクトリをロックしてからコマンドを実行するために使用されます。使用しているオプションは、-e (排他的) と -n (非ブロック) です。

排他的オプションは、ファイルのロックに成功すると、他の誰もそのファイルにアクセスできなくなることを意味します。ノンブロッキング オプションは、ロックの取得に失敗した場合、すぐに試行を中止することを意味します。私たちは一定期間再試行せず、すぐに潔く辞退します。

最初の $0 は、ロックするファイルを示します。この変数は、現在のスクリプトの名前を保持します。

2 番目の $0 は、ロックの取得に成功した場合に実行するコマンドです。繰り返しますが、このスクリプトの名前を渡します。ロックは私たちから離れて全員をロックアウトするため、スクリプト ファイルを起動できます。

起動されるコマンドにパラメータを渡すことができます。 $@ を使用して、このスクリプトに渡されたコマンド ライン パラメーターを、flock によって起動されるスクリプトの新しい呼び出しに渡します。

したがって、このスクリプトはスクリプト ファイルをロックし、それ自体の別のインスタンスを起動します。これはほぼ希望どおりですが、問題があります。 2 番目のインスタンスが終了すると、最初のスクリプトの処理が再開されます。ただし、ご覧のとおり、これに対応する別の工夫が用意されています。

実行中のスクリプトがロックを適用する必要があるかどうかを示すために、GEEKLOCK という環境変数を使用しています。スクリプトが起動されていてロックが設定されていない場合は、ロックを適用する必要があります。スクリプトが起動されており、ロックが設定されている場合は、何もする必要はなく、実行するだけで済みます。スクリプトが実行され、ロックが設定されている場合、スクリプトの他のインスタンスは起動できません。

[ "${GEEKLOCK}" != "$0" ] 

このテストは、「GEEKLOCK 環境変数がスクリプト名に設定されていない場合に true を返す」と解釈されます。このテストは、&& (and) および || によってコマンドの残りの部分に連鎖されます。 (または)。 && 部分はテストが true を返した場合に実行され、|| 部分は実行されます。テストが false を返した場合、セクションが実行されます。

env GEEKLOCK="$0"

env コマンドは、変更された環境で他のコマンドを実行するために使用されます。環境変数 GEEKLOCK を作成し、それをスクリプトの名前に設定することで環境を変更します。 env が起動しようとしているコマンドは flock コマンドであり、flock コマンドはスクリプトの 2 番目のインスタンスを起動します。

スクリプトの 2 番目のインスタンスは、GEEKLOCK 環境変数が存在しないかどうかを確認するチェックを実行しますが、存在することがわかります。 ||コマンドのセクションが実行されます。このセクションにはコロン「:」だけが含まれており、実際には何も行わないコマンドです。実行パスはスクリプトの残りの部分を通過します。

しかし、2 番目のスクリプトが終了したときに最初のスクリプトが独自の処理を続行するという問題がまだ残っています。それを解決するのが exec コマンドです。これにより、呼び出しプロセスが新しく起動されたプロセスに置き換えられて、他のコマンドが実行されます。

exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@" 

したがって、完全なシーケンスは次のとおりです。

  • スクリプトが起動しますが、環境変数が見つかりません。 &&句が実行されます。
  • exec は env を起動し、元のスクリプト プロセスを新しい env プロセスに置き換えます。
  • env プロセスは環境変数を作成し、flock を起動します。
  • flock はスクリプト ファイルをロックし、環境変数を検出するスクリプトの新しいインスタンスを起動し、|| を実行します。句が作成され、スクリプトはその結論まで実行できます。
  • 元のスクリプトは env プロセスに置き換えられたため、存在しなくなり、2 番目のスクリプトが終了すると実行を続行できなくなります。
  • スクリプト ファイルは実行中はロックされるため、flock によって起動されたスクリプトが実行を停止してロックを解放するまで、他のインスタンスを起動できません。

それは『インセプション』のプロットのように聞こえるかもしれないが、それは見事に機能している。この一行は確かにパンチが効いている。

明確にするために、他のインスタンスの起動を妨げるのはスクリプト ファイルのロックであり、環境変数の検出ではありません。環境変数は、実行中のスクリプトに、ロックを設定するか、ロックがすでに設定されていることを指示するだけです。

ロックしてロードする

一度に実行されるスクリプトのインスタンスが 1 つだけであることを確認するのは、予想よりも簡単です。これら 3 つのテクニックはすべて機能します。 flock ワンライナーは操作が最も複雑ですが、スクリプトに組み込むのが最も簡単です。

関連記事: