【Bash】同じシェルスクリプトの二重起動(並列実行)を防止する方法
前書き :同じタイミングで動かれると困る
同じシェルスクリプトが二重起動すると、処理によっては「無駄で時間のかかる処理を二重に行い、システムリソースを消費する事」があります。
例えば、cron/rsyncコマンドを組み合わせてローカルファイル(音楽、動画など)をリモートサーバへ定期バックアップしている場合、一つ前の定期バックアップが終わる前に、次の定期バックアップが開始される可能性があります。タイミング次第では、同じファイルのバックアップを試み、ネットワークやI/Oリソースを無駄に消費してしまいます。
そこで、本記事ではシェルスクリプトの起動時に、同じスクリプトが実行中かどうかを確認する方法を紹介します。
プロセスIDとスクリプト名を用いて二重起動を防止
二重起動を防止するには、以下の処理を行います。
- シェルスクリプトのプロセスIDを取得
- 現在実行中のプロセス一覧に対して、同名のシェルスクリプトが存在するかをpgrepコマンドで検索
実装例は、以下の通りです。
1 2 3 4 5 6 |
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”) ];”部分の判定が正しく動きません。
1 2 3 |
$ pgrep -f bash 11153 13960 16191 |
実行例(検証)
検証用シェルスクリプトとして、only.shスクリプトを用意します。only.shスクリプトは、同名シェルスクリプトが実行済みかをチェックした後に無限ループを行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#!/bin/bash function IsRunning() { if [ $$ -ne $(pgrep -fo "$0") ]; then echo "起動済みです。" exit 1 fi } # 同名シェルスクリプトの実行チェック IsRunning # 無限ループ while true do : done |
検証手順として、
- only.shスクリプトをバックグラウンド実行(&を付けて実行)
- only.shスクリプトをファオアグラウンド実行
を順に行います。
バックグラウンド実行中のonly.shスクリプトは無限ループ中なので、killしない限り実行を継続しています。その状態でonly.shスクリプトを再実行すれば、同名シェルスクリプトの二重起動を検出できます。
以下、実行例です。
1 2 3 4 5 6 7 8 9 10 11 |
(注釈):バックグラウンド実行 $ ./only.sh & [1] 16065 (注釈):フォアグラウンド実行 $ ./only.sh 起動済みです。 (注釈) 二重起動が防止できている。 (注釈かつ補足):pgrepコマンドは、以下のような結果を返している。 $ pgrep -fo "only.sh" 16065 |
二回目のonly.shスクリプトの実行(フォアグラウンド実行)において、二重起動防止ができている事が確認できました。
ロシア人と国際結婚した地方エンジニア。
小学〜大学院、就職の全てが新潟。
大学の専攻は福祉工学だったのに、エンジニアとして就職。新卒入社した会社ではOS開発や半導体露光装置ソフトを開発。現在はサーバーサイドエンジニアとして修行中。HR/HM(メタル)とロシア妻が好き。サイトに関するお問い合わせやTwitterフォローは、お気軽にどうぞ。
ちょっと質問です。cron実行時に多重起動チェックをしたいと思っています。
かんたんで良いのでヒントをいただけないでしょうか。
まだ初心者なので理由はよく分からないのですが、cronで実行した場合にうまくチェックが働きません。
ついでに、cron実行と手動実行が混在する場合のやりかたについても、ヒントをいただけないでしょうか。
>かんたんで良いのでヒントをいただけないでしょうか。
では、簡単にヒントを書きます。
>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が対応できていれば、混在していても問題ない筈です。
縦読みでした。すみません。
こんな内容がシッカリした質問で、縦読みなんて気づけません……
(本名公開しているので、縦読み質問は残します。内容的には、良い気付きになったので)