130

特定のスクリプトの 1 つのインスタンスのみが実行されていることを確認する最も簡単で最良の方法は何ですか? Linux 上の Bash であると仮定します。

現時点で私はやっています:

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh

ただし、いくつかの問題があります。

  1. チェックをスクリプトの外に置きます
  2. 別のアカウントから同じスクリプトを実行することはできません。
  3. -Cプロセス名の最初の 14 文字のみをチェックします

もちろん、独自の pidfile 処理を作成することもできますが、それを行うための簡単な方法が必要だと感じています。

4

14 に答える 14

166

アドバイザリ ロックは古くから使用されており、bash スクリプトで使用できます。私は( from から) よりシンプルなflock(from からutil-linux[-ng])を好みます。そして、これらのスクリプトの終了時のトラップ (sigspec ==または、特定のシグナルをトラップすることは不要です) について常に覚えておいてください。lockfileprocmailEXIT0

2009 年に、ロック可能なスクリプト ボイラープレートをリリースしました (当初は私の wiki ページで入手できましたが、現在は gist として入手できます)。これをユーザーごとに 1 つのインスタンスに変換するのは簡単です。これを使用すると、ロックや同期が必要な他のシナリオのスクリプトを簡単に作成することもできます。

便宜上、前述のボイラープレートを次に示します。

#!/bin/bash
# SPDX-License-Identifier: MIT

## Copyright (C) 2009 Przemyslaw Pawelczyk <przemoc@gmail.com>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate

### HEADER ###

LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99

# PRIVATE
_lock()             { flock -$1 $LOCKFD; }
_no_more_locking()  { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking()  { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }

# ON START
_prepare_locking

# PUBLIC
exlock_now()        { _lock xn; }  # obtain an exclusive lock immediately or fail
exlock()            { _lock x; }   # obtain an exclusive lock
shlock()            { _lock s; }   # obtain a shared lock
unlock()            { _lock u; }   # drop a lock

### BEGIN OF SCRIPT ###

# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1

# Remember! Lock file is removed when one of the scripts exits and it is
#           the only script holding the lock or lock is not acquired at all.
于 2009-12-31T13:57:41.957 に答える
121

スクリプトがすべてのユーザーで同じである場合は、lockfileアプローチを使用できます。ロックを取得した場合は、次に進み、メッセージを表示して終了します。

例として:

[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"

[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $ 

取得後/tmp/the.lock、実行にアクセスできるのはスクリプトだけになります。完了したら、ロックを解除するだけです。スクリプト形式では、これは次のようになります。

#!/bin/bash

lockfile -r 0 /tmp/the.lock || exit 1

# Do stuff here

rm -f /tmp/the.lock
于 2009-11-11T13:25:40.143 に答える
13

bashset -o noclobberオプションを使用して、共通ファイルの上書きを試みます。

この「bash フレンドリー」な手法flockは、利用できない場合や適用できない場合に役立ちます。

簡単な例

if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
    exit 1  # the global.lock already exists
fi

# ... remainder of script ...

より長い例

この例では、ファイルを待機しglobal.lockますが、時間がかかりすぎるとタイムアウトします。

 function lockfile_waithold()
 {
    declare -ir time_beg=$(date '+%s')
    declare -ir time_max=7140  # 7140 s = 1 hour 59 min.
 
    # poll for lock file up to ${time_max}s
    # put debugging info in lock file in case of issues ...
    while ! \
       (set -o noclobber ; \
        echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ 
       ) 2>/dev/null
    do
        if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
            echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
            return 1
        fi
        sleep 1
    done
 
    return 0
 }
 
 function lockfile_release()
 {
    rm -f /tmp/global.lock
 }
 
 if ! lockfile_waithold ; then
      exit 1
 fi
 trap lockfile_release EXIT
 
 # ... remainder of script ...

この手法は、長時間実行されている Ubuntu 16 ホストで確実に機能しました。ホストは、同じ単一のシステム全体の「ロック」ファイルを使用して作業を調整する bash スクリプトの多くのインスタンスを定期的にキューに入れました。

(これは、後で気づいた @Barry Kelly によるこの投稿に似ています。)

于 2015-08-12T04:09:05.450 に答える
4

私はこれをprocmailパッケージの依存関係で見つけました:

apt install liblockfile-bin

走る: dotlockfile -l file.lock

file.lock が作成されます。

ロックを解除するには: dotlockfile -u file.lock

これを使用して、このパッケージ ファイル / コマンドを一覧表示します。 dpkg-query -L liblockfile-bin

于 2016-11-18T06:35:29.230 に答える
3

1行の堅牢なソリューションがあるかどうかはわかりませんので、独自のソリューションを展開することになるかもしれません.

ロックファイルは不完全ですが、'ps | グレップ | grep -v' パイプライン。

そうは言っても、プロセス制御をスクリプトから分離しておくことを検討するかもしれません-開始スクリプトを用意してください。または、少なくとも別のファイルに保持されている関数に分解して、呼び出し元スクリプトに次のものを含めることができます。

. my_script_control.ksh

# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15

制御ロジックを必要とする各スクリプトで。トラップにより、呼び出し元が終了したときにロックファイルが削除されることが保証されるため、スクリプトの各終了ポイントでこれをコーディングする必要はありません。

別の制御スクリプトを使用すると、エッジ ケースのサニティ チェックが可能になります。古いログ ファイルを削除し、ロックファイルが現在実行中のスクリプトのインスタンスに正しく関連付けられていることを確認し、実行中のプロセスを強制終了するオプションを指定します。psまた、出力で grep を正常に使用できる可能性が高くなることも意味します。ps-grep を使用して、ロックファイルに関連付けられた実行中のプロセスがあることを確認できます。おそらく、プロセスに関する情報を含む何らかの方法でロックファイルに名前を付けることができます: user、pid など。これは、後でスクリプトを呼び出して、ロックファイルを作成したプロセスがまだ存在するかどうかを判断するために使用できます。

于 2009-11-11T15:13:16.647 に答える
2

chpst (runit の一部)も参照することをお勧めします。

chpst -L /tmp/your-lockfile.loc ./script.name.sh
于 2013-06-05T11:23:29.907 に答える
1

Ubuntu/Debian ディストリビューションには、start-stop-daemon説明したのと同じ目的のツールがあります。/etc/init.d/skeletonも参照して、開始/停止スクリプトの作成にどのように使用されるかを確認してください。

-- ノア

于 2010-05-19T11:55:16.060 に答える
0

私は同じ問題を抱えており、lockfile、プロセス ID 番号を保持する pid ファイル、および中止されたスクリプトが次の実行を停止しないようにするチェックを使用するテンプレートを思いつきました。kill -0 $(cat $pid_file)これにより、lockfile と pid ファイルが存在する /tmp に foobar-$USERID フォルダーが作成されます。

これらのアクションを に保持している限り、スクリプトを呼び出して他のことを行うことができますalertRunningPS

#!/bin/bash

user_id_num=$(id -u)
pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid"
lock_file="/tmp/foobar-$user_id_num/running.lock"
ps_id=$$

function alertRunningPS () {
    local PID=$(cat "$pid_file" 2> /dev/null)
    echo "Lockfile present. ps id file: $PID"
    echo "Checking if process is actually running or something left over from crash..."
    if kill -0 $PID 2> /dev/null; then
        echo "Already running, exiting"
        exit 1
    else
        echo "Not running, removing lock and continuing"
        rm -f "$lock_file"
        lockfile -r 0 "$lock_file"
    fi
}

echo "Hello, checking some stuff before locking stuff"

# Lock further operations to one process
mkdir -p /tmp/foobar-$user_id_num
lockfile -r 0 "$lock_file" || alertRunningPS

# Do stuff here
echo -n $ps_id > "$pid_file"
echo "Running stuff in ONE ps"

sleep 30s

rm -f "$lock_file"
rm -f "$pid_file"
exit 0
于 2014-01-30T10:47:42.300 に答える
-2

「システムごとに 1 つのスクリプトのコピー」を処理する非常に簡単な方法を見つけました。ただし、多くのアカウントからスクリプトの複数のコピーを実行することはできません (標準の Linux では)。

解決:

スクリプトの冒頭で、次のように指定しました。

pidof -s -o '%PPID' -x $( basename $0 ) > /dev/null 2>&1 && exit

どうやらpidofは、次のようにうまく機能します。

  • のようなプログラム名に制限はありませんps -C ...
  • 私がする必要はありませんgrep -v grep(または同様のもの)

また、ロックファイルに依存していません。これは私にとって大きなメリットです。それらを中継すると、古いロックファイルの処理を追加する必要があるためです。これはそれほど複雑ではありませんが、回避できるのであれば、なぜですか?

「実行中のユーザーごとに1つのスクリプトのコピー」のチェックについては、これを書きましたが、あまり満足していません。

(
    pidof -s -o '%PPID' -x $( basename $0 ) | tr ' ' '\n'
    ps xo pid= | tr -cd '[0-9\n]'
) | sort | uniq -d

次に、その出力を確認します-空の場合-同じユーザーからのスクリプトのコピーはありません。

于 2009-11-12T11:58:40.017 に答える
-3

スクリプトから:

ps -ef | grep $0 | grep $(whoami)
于 2009-11-11T13:26:27.990 に答える
-3

これが当社の標準ビットです。ロックファイルをクリーンアップせずに、何らかの形で停止したスクリプトから回復できます。

正常に実行されている場合は、プロセス ID をロック ファイルに書き込みます。実行開始時にロック ファイルが見つかった場合は、ロック ファイルからプロセス ID を読み取り、そのプロセスが存在するかどうかを確認します。プロセスが存在しない場合は、古いロック ファイルを削除して処理を続行します。ロックファイルが存在し、プロセスがまだ実行されている場合にのみ、プロセスは終了します。そして、終了時にメッセージを書き込みます。

# lock to ensure we don't get two copies of the same job
script_name="myscript.sh"
lock="/var/run/${script_name}.pid"
if [[ -e "${lock}" ]]; then
    pid=$(cat ${lock})
    if [[ -e /proc/${pid} ]]; then
        echo "${script_name}: Process ${pid} is still running, exiting."
        exit 1
    else
        # Clean up previous lock file
        rm -f ${lock}
   fi
fi
trap "rm -f ${lock}; exit $?" INT TERM EXIT
# write $$ (PID) to the lock file
echo "$$" > ${lock}
于 2015-05-22T18:21:54.587 に答える