bashスクリプトの1つのインスタンスのみが実行されるようにする方法は?


26

追加のツールを必要としないソリューションが推奨されます。


ロックファイルはどうですか?
マルコ

@マルコ私はそれを使用してこのSOの答えを見つけましたが、コメント記載されているように、これは競合状態を作成することができます
トビアス・キーンズラー

3
これはBashFAQ 45です。
jw013

@ jw013ありがとう!そのため、おそらくのようなものは、ln -s my.pid .lockロックを要求します(続くecho $$ > my.pid)、失敗にに保存されているPIDがいるかどうかを確認することができ.lock、本当にスクリプトのアクティブなインスタンスである
トビアスKienzler

回答:


18

nsgの答えとほぼ同じ:ロックディレクトリを使用します。ディレクトリの作成は、linux、unix、* BSD、および他の多くのOSでアトミックです。

if mkdir $LOCKDIR
then
    # Do important, exclusive stuff
    if rmdir $LOCKDIR
    then
        echo "Victory is mine"
    else
        echo "Could not remove lock dir" >&2
    fi
else
    # Handle error condition
    ...
fi

デバッグのために、ロックするshのPIDをロックディレクトリのファイルに入れることができますが、そのPIDをチェックしてロックプロセスがまだ実行されているかどうかを確認できるとは思わないでください。多くの競合状態がその道をたどります。


1
格納されたPIDを使用して、ロックインスタンスがまだ生きているかどうかを確認することを検討します。しかし、ここでの主張だmkdirNFS上のアトミックではありません(私の場合ではないですが、私は、人はそれを言及する必要があります推測する場合はtrue)
トビアスKienzler

はい。必ず、保存されたPIDを使用して、ロックプロセスがまだ実行されているかどうかを確認しますが、メッセージを記録する以外のことは行わないでください。格納されたPIDの確認、新しいPIDファイルの作成などの作業により、レースの大きな窓が開けます。
ブルースエディガー

わかりましたIhunathが述べたように、lockdir /tmpは通常NFS共有ではない可能性が高いので、それで問題ないはずです。
トビアスキンツラー

rm -rfロックディレクトリを削除するために使用します。rmdir誰か(必ずしもあなたではない)がファイルをディレクトリに追加できた場合は失敗します。
chepner

18

Bruce Edigerの回答に追加し、この回答に触発され、スクリプトの終了を防ぐためにクリーンアップにさらにスマートを追加する必要があります。

#Remove the lock directory
function cleanup {
    if rmdir $LOCKDIR; then
        echo "Finished"
    else
        echo "Failed to remove lock directory '$LOCKDIR'"
        exit 1
    fi
}

if mkdir $LOCKDIR; then
    #Ensure that if we "grabbed a lock", we release it
    #Works for SIGTERM and SIGINT(Ctrl-C)
    trap "cleanup" EXIT

    echo "Acquired lock, running"

    # Processing starts here
else
    echo "Could not create lock directory '$LOCKDIR'"
    exit 1
fi

また、if ! mkdir "$LOCKDIR"; then handle failure to lock and exit; fi trap and do processing after if-statement
クサラナナンダ

6

これはあまりにも単純すぎるかもしれません。間違っている場合は修正してください。単純ではありませんpsか?

#!/bin/bash 

me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;

# do stuff below this comment

1
素敵で素晴らしい。:)
不気味な

1
私はこの条件を1週間使用しましたが、2回は新しいプロセスの開始を妨げませんでした。私は問題が何であるかを考えました-新しいpidは古いものの部分文字列であり、によって隠されgrep -v $$ます 実際の例:古い- 14532、新しい- 1453年、旧- 28858、新しい- 858
Naktibalda

私はそれを次のように変更grep -v $$して修正しましたgrep -v "^${$} "
ナクティバルダ

@Naktibalda良いキャッチ、ありがとう!修正することもできますgrep -wv "^$$"(編集を参照)。
テルドン

その更新をありがとう。短いPIDにはスペースが埋め込まれているため、パターンが時々失敗しました。
ナクティバルダ

4

マルコが述べたように、ロックファイルを使用します

#!/bin/bash

# Exit if /tmp/lock.file exists
[ -f /tmp/lock.file ] && exit

# Create lock file, sleep 1 sec and verify lock
echo $$ > /tmp/lock.file
sleep 1
[ "x$(cat /tmp/lock.file)" == "x"$$ ] || exit

# Do stuff
sleep 60

# Remove lock file
rm /tmp/lock.file

1
(ロックファイルを作成するのを忘れたと思います)競合状態はどうですか?
トビアスキンツラー

ops :)はい、私の例では競合状態は問題です。私は通常1時間ごとまたは1日ごとのcronジョブを作成し、競合状態はまれです。
nsg

私の場合も関係ありませんが、心に留めておくべきことです。使用するlsof $0ことも悪くないのでしょうか?
トビアスキンツラー

$$ロックファイルに書き込みを行うことにより、競合状態を減らすことができます。その後sleep、短い間隔で、それを読み返します。PIDがまだ自分のものである場合は、ロックを正常に取得しています。追加のツールはまったく必要ありません。
マナトワーク

1
この目的でlsofを使用したことはありません。これは動作するはずです。私のシステムではlsofが非常に遅い(1〜2秒)ことに注意してください。競合状態には多くの時間がかかる可能性が高いです。
NSG

3

スクリプトの1つのインスタンスのみが実行されていることを確認する場合は、以下をご覧ください。

スクリプトをロックします(並列実行に対して)

それ以外の場合は、追加のツールとは呼ばないpsためlsof <full-path-of-your-script>、チェックまたは起動できます。


補足

実際に私はこのようにすることを考えました:

for LINE in `lsof -c <your_script> -F p`; do 
    if [ $$ -gt ${LINE#?} ] ; then
        echo "'$0' is already running" 1>&2
        exit 1;
    fi
done

これにより、pid複数のインスタンスを<your_script>同時にfork-execした場合でも、最低のプロセスのみが実行され続けます。


1
リンクをお寄せいただきありがとうございますが、重要な部分を回答に含めることができますか?これは、リンク切れを防ぐために、SEで共通のポリシーだ...しかし、のようなもの[[(lsof $0 | wc -l) > 2]] && exitかもしれないが、実際には十分であるか、またはこれはまた、競合状態になりやすいのですか?
トビアスキンツラー

あなたは私の答えの本質的な部分が欠落していたことは正しいですし、リンクを投稿するだけでかなり不自由です。私は自分の提案を答えに加えました。
user1146332

3

bashスクリプトの単一インスタンスが実行されることを確認するもう1つの方法:

#!/bin/bash

# Check if another instance of script is running
pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1

...

pidof -o %PPID -x $0 既存のスクリプトが既に実行されている場合は既存のスクリプトのPIDを取得し、他のスクリプトが実行されていない場合はエラーコード1で終了します


3

追加のツールなしで解決策を求めてきましたが、これは私が使用する私のお気に入りの方法flockです:

#!/bin/sh

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

echo "servus!"
sleep 10

これはの例のセクションから来ていman flockます。

これは、シェルスクリプトの便利な定型コードです。ロックするシェルスクリプトの先頭に配置すると、最初の実行時に自動的にロックされます。実行されているシェルスクリプトにenv var $ FLOCKERが設定されていない場合、flockを実行し、適切な引数で再実行する前に排他的な非ブロックロックを取得します(ロックファイルとしてスクリプト自体を使用)。また、FLOCKER env varを正しい値に設定して、再度実行されないようにします。

考慮すべき点:

  • が必要ですflock。見つからない場合、サンプルスクリプトはエラーで終了します
  • 余分なロックファイルは不要
  • スクリプトがNFS上にある場合は機能しない場合があります(/server/66919/file-locks-on-an-nfsを参照)

/programming/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-atも参照してください


1

これは、Anselmo's Answerの修正版です。アイデアは、bashスクリプト自体を使用flockして読み取り専用ファイル記述子を作成し、ロックの処理に使用することです。

SCRIPT=`realpath $0`     # get absolute path to the script itself
exec 6< "$SCRIPT"        # open bash script using file descriptor 6
flock -n 6 || { echo "ERROR: script is already running" && exit 1; }   # lock file descriptor 6 OR show error message if script is already running

echo "Run your single instance code here"

他のすべての答えとの主な違いは、このコードはファイルシステムを変更せず、非常に低いフットプリントを使用し、スクリプトが終了状態に関係なく終了するとすぐにファイル記述子が閉じられるため、クリーンアップを必要としないことです。したがって、スクリプトが失敗するか成功するかは問題ではありません。


正当な理由がない限り、すべてのシェル変数参照を常に引用する必要があります。また、自分が何をしているのかを確実に知っている必要があります。だからあなたはやっている必要がありますexec 6< "$SCRIPT"
スコット

@Scott私はあなたの提案に従ってコードを変更しました。どうもありがとう。
ジョン・ドウ

1

cksumを使用して、ファイル名とファイルパスを変更しても、スクリプトが本当に単一のインスタンスを実行していることを確認しています

サーバーが突然ダウンした場合、サーバーが起動した後に手動でロックファイルを削除する必要があるため、トラップとロックファイルを使用していません。

注:grep psには、最初の行の#!/ bin / bashが必要です

#!/bin/bash

checkinstance(){
   nprog=0
   mysum=$(cksum $0|awk '{print $1}')
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(cksum /proc/$i/cwd/$cmd|awk '{print $1}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance 

#--- run your script bellow 

echo pass
while true;do sleep 1000;done

または、スクリプト内でcksumをハードコーディングできるので、スクリプトのファイル名、パス、またはコンテンツを変更する場合に心配する必要はありません

#!/bin/bash

mysum=1174212411

checkinstance(){
   nprog=0
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(grep mysum /proc/$i/cwd/$cmd|head -1|awk -F= '{print $2}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance

#--- run your script bellow

echo pass
while true;do sleep 1000;done

1
チェックサムをどのようにハードコーディングするのが良いアイデアであるかを正確に説明してください。
スコット

チェックサムをハードコーディングするのではなく、スクリプトのIDキーを作成するだけで、別のインスタンスが実行されると、他のシェルスクリプトプロセスをチェックし、IDファイルがそのファイルにある場合は最初にファイルをcatするため、インスタンスは既に実行されています。
アルプトラ

OK; 回答を編集して説明してください。そして、将来的には、コードのポストではない複数の30行の長いブロック彼らしているように見えるが(ほぼ)同じことなく、それを行ってくださいと言って説明どのようにしている異なっを。また、「スクリプト内で[sic] cksumをハードコード化できます」などのことを言ったり、チェックサムについてもう話さない場合は、変数名mysumとを使用し続けたりしないでfsumください。
スコット

面白そうですね、ありがとう!unix.stackexchangeへようこそ:)
トビアス・キエンツラー


0

あなたへの私のコード

#!/bin/bash

script_file="$(/bin/readlink -f $0)"
lock_file=${script_file////_}

function executing {
  echo "'${script_file}' already executing"
  exit 1
}

(
  flock -n 9 || executing

  sleep 10

) 9> /var/lock/${lock_file}

に基づいてman flock、改善のみ:

  • スクリプトのフルネームに基づいたロックファイルの名前
  • メッセージ executing

ここにを配置するとsleep 10、すべてのメインスクリプトを配置できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.