web-dev-qa-db-ja.com

Bashスクリプトの1つのインスタンスのみが実行されていることを確認する最良の方法は何ですか?

特定のスクリプトの1つのインスタンスのみが実行されていることを確認するための最も単純/最良の方法は何ですか-LinuxでBashを想定していますか?

現在、私はやっています:

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

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

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

もちろん、独自のpidfile処理を記述できますが、簡単な方法があるはずです。

96
user80168

スクリプトがすべてのユーザーで同じ場合、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
96
ezpz

アドバイザリロックは古くから使用されており、bashスクリプトで使用できます。シンプルなflockutil-linux[-ng]lockfileから(procmailから)。そして、終了時のトラップについて常に覚えておいてください(sigspec == EXITまたは0、これらのスクリプトでは特定の信号をトラップすることは不要です。

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

便宜上、前述の定型文を示します。

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

## Copyright (C) 2009 Przemyslaw Pawelczyk <[email protected]>
##
## 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.
136
przemoc

flockはおそらく最も簡単な(そして最も記憶に残る)バリアントだと思います。 dvds および cds を自動エンコードするためにcronジョブで使用します

# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock   my_bash_command

つかいます -wタイムアウトの場合、またはロックが解除されるまで待機するオプションを省略します。最後に、manページには複数のコマンドの素晴らしい例が示されています。

   (
     flock -n 9 || exit 1
     # ... commands executed under lock ...
   ) 9>/var/lock/mylockfile
31
Jake Biesinger

使用 set -o noclobberオプションを使用して、共通ファイルを上書きしようとします。

短い例

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 ...


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

7

1行の堅牢なソリューションがあるかどうかはわかりません。そのため、最終的には独自のソリューションを導入することになります。

ロックファイルは不完全ですが、 'ps | grep | 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

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

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

3
martin clayton

最初のテスト例

[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"

2番目のテスト例

currsh=$0
currpid=$$
runpid=$(lsof -t $currsh| paste -s -d " ")
if [[ $runpid == $currpid ]]
then
  sleep 11111111111111111
else
  echo -e "\nPID($runpid)($currpid) ::: At least one of \"$currsh\" is running !!!\n"
  false
  exit 1
fi

説明

「lsof -t」は、「$ 0」という名前の現在実行中のスクリプトのすべてのPIDをリストします。

コマンド「lsof」には2つの利点があります。

  1. Vimは「.file.swp」などのマッピングファイルを編集するため、vimなどのエディターで編集しているPIDは無視します。
  2. 現在実行中のシェルスクリプトによって分岐されたPIDを無視します。ほとんどの「grep」派生コマンドでは実行できません。 「pstree -pH pidnum」コマンドを使用して、現在のプロセス分岐ステータスに関する詳細を表示します。
2
yanyingwang

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

apt install liblockfile-bin

走る: dotlockfile -l file.lock

file.lockが作成されます。

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

これを使用して、このパッケージファイル/コマンドをリストします:dpkg-query -L liblockfile-bin

2
James Tan

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

-ノア

1
Noah Spurrier

ワンライン究極のソリューション:

[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"
1
Dm1

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

chpst -L /tmp/your-lockfile.loc ./script.name.sh
1
rud

私は同じ問題を抱えていて、ロックファイルを使用する template と、プロセスID番号を保持するpidファイルと、中止されたスクリプトが停止しないようにするkill -0 $(cat $pid_file)チェックを思い付きました。次の実行。これにより、ロックファイルと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
0
Erik Lundmark