194

特定の時間にシェル スクリプトのインスタンスが 1 つしか実行されないようにする簡単な方法は何ですか?

4

41 に答える 41

232

flock(1)ファイル記述子で排他的スコープ ロックを作成するために使用します。このようにして、スクリプトのさまざまな部分を同期することもできます。

#!/bin/bash

(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200 || exit 1

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

(これにより、との間のコードが)一度に 1 つのプロセスによってのみ実行され、プロセスがロックを長時間待機しないことが保証されます。

警告: この特定のコマンドは の一部ですutil-linux。Linux 以外のオペレーティング システムを実行している場合は、利用できる場合と利用できない場合があります。

于 2008-10-04T08:20:19.143 に答える
166

「ロックファイル」の存在をテストする素朴なアプローチには欠陥があります。

なんで?ファイルが存在するかどうかをチェックせず、単一のアトミック アクションでファイルを作成するためです。このため; 相互排除の試みを中断させる競合状態があります。

代わりに、 を使用できますmkdirmkdirディレクトリがまだ存在しない場合は作成し、存在する場合は終了コードを設定します。さらに重要なことは、これらすべてを単一のアトミック アクションで実行できるため、このシナリオに最適です。

if ! mkdir /tmp/myscript.lock 2>/dev/null; then
    echo "Myscript is already running." >&2
    exit 1
fi

すべての詳細については、優れた BashFAQ を参照してください: http://mywiki.wooledge.org/BashFAQ/045

古くなったロックを処理したい場合は、fuser(1)が便利です。ここでの唯一の欠点は、操作に約 1 秒かかるため、すぐに実行できないことです。

これは、フューザーを使用して問題を解決する、私が一度書いた関数です。

#       mutex file
#
# Open a mutual exclusion lock on the file, unless another process already owns one.
#
# If the file is already locked by another process, the operation fails.
# This function defines a lock on a file as having a file descriptor open to the file.
# This function uses FD 9 to open a lock on the file.  To release the lock, close FD 9:
# exec 9>&-
#
mutex() {
    local file=$1 pid pids 

    exec 9>>"$file"
    { pids=$(fuser -f "$file"); } 2>&- 9>&- 
    for pid in $pids; do
        [[ $pid = $$ ]] && continue

        exec 9>&- 
        return 1 # Locked by a pid.
    done 
}

次のようなスクリプトで使用できます。

mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }

移植性を気にしない場合 (これらのソリューションはほとんどすべての UNIX ボックスで動作するはずです)、Linux のfuser(1)はいくつかの追加オプションを提供し、flock(1)もあります。

于 2009-04-08T20:16:28.130 に答える
121

これは、ロックファイルを使用し、それに PID をエコーする実装です。これは、 pidfileを削除する前にプロセスが強制終了された場合の保護として機能します。

LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "already running"
    exit
fi

# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}

# do stuff
sleep 1000

rm -f ${LOCKFILE}

ここでのトリックは、kill -0シグナルを送信せず、指定された PID を持つプロセスが存在するかどうかを確認するだけです。また、 を呼び出すと、プロセスが強制終了された場合でもロックファイルtrapが確実に削除されます ( を除く)。kill -9

于 2008-10-09T00:24:09.850 に答える
43

flock(2) システムコールの周りには、想像を絶する flock(1) と呼ばれるラッパーがあります。これにより、クリーンアップなどを心配することなく、排他ロックを確実に取得することが比較的簡単になります。シェル スクリプトでの使用方法については、man ページに例があります。

于 2008-10-09T00:26:26.320 に答える
28

ロックの信頼性を高めるには、不可分操作が必要です。上記の提案の多くはアトミックではありません。提案されたlockfile(1)ユーティリティは、マンページで言及されているように、その「NFS耐性」として有望に見えます。OSがlockfile(1)をサポートしておらず、ソリューションがNFSで動作する必要がある場合、選択肢は多くありません。

NFSv2には2つのアトミック操作があります。

  • シンボリックリンク
  • 名前を変更

NFSv3では、create呼び出しもアトミックです。

ディレクトリ操作は、NFSv2およびNFSv3ではアトミックではありません(Brent Callaghanによる「NFSIllustrated」という本、ISBN 0-201-32570-5を参照してください。BrentはSunのNFSベテランです)。

これを知っていると、ファイルとディレクトリのスピンロックを実装できます(PHPではなくシェルで):

現在のディレクトリをロックします。

while ! ln -s . lock; do :; done

ファイルをロックする:

while ! ln -s ${f} ${f}.lock; do :; done

現在のディレクトリのロックを解除します(実行中のプロセスが実際にロックを取得したと仮定します):

mv lock deleteme && rm deleteme

ファイルのロックを解除します(実行中のプロセスが実際にロックを取得したと仮定します)。

mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme

Removeもアトミックではないため、最初に名前の変更(アトミック)を実行してから、removeを実行します。

シンボリックリンクと名前変更の呼び出しでは、両方のファイル名が同じファイルシステムに存在する必要があります。私の提案:単純なファイル名(パスなし)のみを使用し、ファイルとロックを同じディレクトリに配置します。

于 2008-11-29T20:46:15.077 に答える
27

flock のようなアトミック操作が必要です。そうしないと、最終的に失敗します。

しかし、群れが利用できない場合はどうすればよいですか。さて、mkdirがあります。それもアトミック操作です。mkdir が成功するプロセスは 1 つだけで、他のプロセスはすべて失敗します。

したがって、コードは次のとおりです。

if mkdir /var/lock/.myscript.exclusivelock
then
  # do stuff
  :
  rmdir /var/lock/.myscript.exclusivelock
fi

古くなったロックを処理する必要があります。そうしないと、クラッシュ後にスクリプトが再び実行されなくなります。

于 2009-10-13T14:39:25.137 に答える
24

別のオプションは、 を実行してシェルのnoclobberオプションを使用することset -Cです。>ファイルが既に存在する場合は失敗します。

簡単に言えば:

set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
    echo "Successfully acquired lock"
    # do work
    rm "$lockfile"    # XXX or via trap - see below
else
    echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi

これにより、シェルは次を呼び出します。

open(pathname, O_CREAT|O_EXCL)

ファイルをアトミックに作成するか、ファイルが既に存在する場合は失敗します。


BashFAQ 045のコメントによると、これは で失敗する可能性がありksh88ますが、私のすべてのシェルで動作します:

$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

pdkshフラグを追加するのは興味深いですがO_TRUNC、明らかに冗長
です。空のファイルを作成しているか、何もしていないかのどちらかです。


どのように行うかは、rm汚れた出口をどのように処理するかによって異なります。

クリーンな終了時に削除

クリーンでない終了の原因となった問題が解決され、ロックファイルが手動で削除されるまで、新しい実行は失敗します。

# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"

任意の出口で削除

スクリプトがまだ実行されていなければ、新しい実行は成功します。

trap 'rm "$lockfile"' EXIT
于 2011-02-25T01:55:16.937 に答える
23

GNU Parallelとして呼び出されたときにミューテックスとして機能するため、これに使用できますsem。したがって、具体的には、次を使用できます。

sem --id SCRIPTSINGLETON yourScript

タイムアウトも必要な場合は、次を使用します。

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

タイムアウト <0 は、セマフォがタイムアウト内に解放されない場合、スクリプトを実行せずに終了することを意味し、タイムアウト >0 は、とにかくスクリプトを実行することを意味します。

名前を付ける必要があることに注意してください( を使用--id)。そうしないと、デフォルトで制御端末になります。

GNU Parallelほとんどの Linux/OSX/Unix プラットフォームに簡単にインストールできます。これは単なる Perl スクリプトです。

于 2016-05-18T14:49:53.050 に答える
16

シェル スクリプトの場合、ロックの移植性が向上するため、私はmkdiroverを使用する傾向があります。flock

いずれにせよ、使用set -eするだけでは十分ではありません。コマンドが失敗した場合にのみスクリプトを終了します。あなたのロックはまだ取り残されます。

適切なロックのクリーンアップのために、トラップを次の擬似コードのようなものに設定する必要があります (持ち上げられ、単純化され、テストされていませんが、アクティブに使用されているスクリプトから):

#=======================================================================
# Predefined Global Variables
#=======================================================================

TMPDIR=/tmp/myapp
[[ ! -d $TMP_DIR ]] \
    && mkdir -p $TMP_DIR \
    && chmod 700 $TMPDIR

LOCK_DIR=$TMP_DIR/lock

#=======================================================================
# Functions
#=======================================================================

function mklock {
    __lockdir="$LOCK_DIR/$(date +%s.%N).$$" # Private Global. Use Epoch.Nano.PID

    # If it can create $LOCK_DIR then no other instance is running
    if $(mkdir $LOCK_DIR)
    then
        mkdir $__lockdir  # create this instance's specific lock in queue
        LOCK_EXISTS=true  # Global
    else
        echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
        exit 1001  # Or work out some sleep_while_execution_lock elsewhere
    fi
}

function rmlock {
    [[ ! -d $__lockdir ]] \
        && echo "WARNING: Lock is missing. $__lockdir does not exist" \
        || rmdir $__lockdir
}

#-----------------------------------------------------------------------
# Private Signal Traps Functions {{{2
#
# DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or 
#         there will be *NO CLEAN UP*. You'll have to manually remove 
#         any locks in place.
#-----------------------------------------------------------------------
function __sig_exit {

    # Place your clean up logic here 

    # Remove the LOCK
    [[ -n $LOCK_EXISTS ]] && rmlock
}

function __sig_int {
    echo "WARNING: SIGINT caught"    
    exit 1002
}

function __sig_quit {
    echo "SIGQUIT caught"
    exit 1003
}

function __sig_term {
    echo "WARNING: SIGTERM caught"    
    exit 1015
}

#=======================================================================
# Main
#=======================================================================

# Set TRAPs
trap __sig_exit EXIT    # SIGEXIT
trap __sig_int INT      # SIGINT
trap __sig_quit QUIT    # SIGQUIT
trap __sig_term TERM    # SIGTERM

mklock

# CODE

exit # No need for cleanup code here being in the __sig_exit trap function

これが何が起こるかです。すべてのトラップは出口を生成するため、__sig_exitロックをクリーンアップする関数が常に発生します (SIGKILL を除く)。

注: 私の終了値は低い値ではありません。なんで?さまざまなバッチ処理システムが、0 から 31 までの数字を作成または想定しています。これらを別の値に設定すると、スクリプトとバッチ ストリームを前のバッチ ジョブまたはスクリプトに応じて反応させることができます。

于 2011-10-28T21:19:10.140 に答える
14

本当に速くて本当に汚い?スクリプトの上部にあるこのワンライナーは機能します。

[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit

もちろん、スクリプト名が一意であることを確認してください。:)

于 2013-09-07T06:55:16.330 に答える
7

これは、アトミック ディレクトリ ロックと PID による古いロックのチェックを組み合わせ、古い場合は再起動するアプローチです。また、これはバシズムに依存していません。

#!/bin/dash

SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"

if ! mkdir $LOCKDIR 2>/dev/null
then
    # lock failed, but check for stale one by checking if the PID is really existing
    PID=$(cat $PIDFILE)
    if ! kill -0 $PID 2>/dev/null
    then
       echo "Removing stale lock of nonexistent PID ${PID}" >&2
       rm -rf $LOCKDIR
       echo "Restarting myself (${SCRIPTNAME})" >&2
       exec "$0" "$@"
    fi
    echo "$SCRIPTNAME is already running, bailing out" >&2
    exit 1
else
    # lock successfully acquired, save PID
    echo $$ > $PIDFILE
fi

trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT


echo hello

sleep 30s

echo bye
于 2014-08-11T12:56:18.717 に答える
5

既知の場所にロック ファイルを作成し、スクリプトの開始時に存在を確認しますか? PID をファイルに入れると、誰かがスクリプトの実行を妨げている誤ったインスタンスを追跡しようとしている場合に役立ちます。

于 2008-10-09T00:17:00.223 に答える
4

lockfile-progsDebian マシンを対象とする場合、このパッケージは優れたソリューションであることがわかりました。procmailツールも付いていlockfileます。ただし、これらのどちらにもこだわらないことがあります。

mkdirこれは、アトミック性と PID ファイルを使用して古いロックを検出する私のソリューションです。このコードは現在、Cygwin のセットアップで運用されており、うまく機能します。

exclusive_lock_requireそれを使用するには、何かへの排他的アクセスが必要なときに呼び出すだけです。オプションのロック名パラメーターを使用すると、異なるスクリプト間でロックを共有できます。より複雑なものが必要な場合に備えて、2 つの低レベル関数 (exclusive_lock_tryおよび) もあります。exclusive_lock_retry

function exclusive_lock_try() # [lockname]
{

    local LOCK_NAME="${1:-`basename $0`}"

    LOCK_DIR="/tmp/.${LOCK_NAME}.lock"
    local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"

    if [ -e "$LOCK_DIR" ]
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        if [ ! -z "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2> /dev/null
        then
            # locked by non-dead process
            echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
            return 1
        else
            # orphaned lock, take it over
            ( echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null && local LOCK_PID="$$"
        fi
    fi
    if [ "`trap -p EXIT`" != "" ]
    then
        # already have an EXIT trap
        echo "Cannot get lock, already have an EXIT trap"
        return 1
    fi
    if [ "$LOCK_PID" != "$$" ] &&
        ! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        # unable to acquire lock, new process got in first
        echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
        return 1
    fi
    trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT

    return 0 # got lock

}

function exclusive_lock_retry() # [lockname] [retries] [delay]
{

    local LOCK_NAME="$1"
    local MAX_TRIES="${2:-5}"
    local DELAY="${3:-2}"

    local TRIES=0
    local LOCK_RETVAL

    while [ "$TRIES" -lt "$MAX_TRIES" ]
    do

        if [ "$TRIES" -gt 0 ]
        then
            sleep "$DELAY"
        fi
        local TRIES=$(( $TRIES + 1 ))

        if [ "$TRIES" -lt "$MAX_TRIES" ]
        then
            exclusive_lock_try "$LOCK_NAME" > /dev/null
        else
            exclusive_lock_try "$LOCK_NAME"
        fi
        LOCK_RETVAL="${PIPESTATUS[0]}"

        if [ "$LOCK_RETVAL" -eq 0 ]
        then
            return 0
        fi

    done

    return "$LOCK_RETVAL"

}

function exclusive_lock_require() # [lockname] [retries] [delay]
{
    if ! exclusive_lock_retry "$@"
    then
        exit 1
    fi
}
于 2009-05-23T09:39:06.480 に答える
4

このスレッドの他の場所で既に説明されている flock の制限が問題にならない場合は、次のようにします。

#!/bin/bash

{
    # exit if we are unable to obtain a lock; this would happen if 
    # the script is already running elsewhere
    # note: -x (exclusive) is the default
    flock -n 100 || exit

    # put commands to run here
    sleep 100
} 100>/tmp/myjob.lock 
于 2012-05-03T18:16:49.007 に答える
4

スクリプトの先頭にこの行を追加します

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

man flock の定型コードです。

さらにログが必要な場合は、これを使用してください

[ "${FLOCKER}" != "$0" ] && { echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo "Lock is free. Completing."

flockこれは、ユーティリティを使用してロックを設定およびチェックします。このコードは、FLOCKER 変数をチェックして初めて実行されたかどうかを検出します。スクリプト名に設定されていない場合は、flock を使用して FLOCKER 変数を初期化して再帰的にスクリプトを再起動しようとします。FLOCKER が正しく設定されている場合は、前の反復で flock します。成功し、続行しても問題ありません。ロックがビジーの場合、構成可能な終了コードで失敗します。

Debian 7 では動作しないようですが、実験的な util-linux 2.25 パッケージで再び動作するようです。「flock: ... Text file busy」と書かれています。スクリプトの書き込み権限を無効にすることでオーバーライドできます。

于 2014-08-05T07:31:47.853 に答える
3

私はロックファイル、ロックディレクトリ、特別なロック プログラムを廃止したいと考えていpidofました。また、可能な限り単純なコード (または少なくとも可能な限り少ない行) が必要でした。1 行の最も単純ifなステートメント:

if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
于 2016-10-20T03:59:46.097 に答える
3

古いロック ファイルを処理する単純なアプローチを使用します。

pid を格納する上記のソリューションの一部は、pid がラップアラウンドできるという事実を無視していることに注意してください。そのため、保存された pid を持つ有効なプロセスがあるかどうかを確認するだけでは、特に長時間実行されるスクリプトの場合は十分ではありません。

noclobber を使用して、一度に 1 つのスクリプトだけが開いてロック ファイルに書き込むことができるようにします。さらに、プロセスを一意に識別するのに十分な情報をロックファイルに保存します。プロセスを一意に識別するデータのセットを pid、ppid、lstart と定義します。

新しいスクリプトの起動時に、ロック ファイルの作成に失敗した場合は、ロック ファイルを作成したプロセスがまだ残っていることを確認します。そうでない場合は、元のプロセスが不審な死を遂げ、古いロック ファイルが残っていると見なされます。その後、新しいスクリプトがロック ファイルの所有権を取得し、すべてがうまくいきます。

複数のプラットフォームにわたって複数のシェルで動作するはずです。高速、ポータブル、シンプル。

#!/usr/bin/env sh
# Author: rouble

LOCKFILE=/var/tmp/lockfile #customize this line

trap release INT TERM EXIT

# Creates a lockfile. Sets global variable $ACQUIRED to true on success.
# 
# Returns 0 if it is successfully able to create lockfile.
acquire () {
    set -C #Shell noclobber option. If file exists, > will fail.
    UUID=`ps -eo pid,ppid,lstart $$ | tail -1`
    if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
        ACQUIRED="TRUE"
        return 0
    else
        if [ -e $LOCKFILE ]; then 
            # We may be dealing with a stale lock file.
            # Bring out the magnifying glass. 
            CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
            CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
            CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`
            if [ "$CURRENT_UUID_FROM_LOCKFILE" == "$CURRENT_UUID_FROM_PS" ]; then 
                echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE" >&2
                return 1
            else
                # The process that created this lock file died an ungraceful death. 
                # Take ownership of the lock file.
                echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
                release "FORCE"
                if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
                    ACQUIRED="TRUE"
                    return 0
                else
                    echo "Cannot write to $LOCKFILE. Error." >&2
                    return 1
                fi
            fi
        else
            echo "Do you have write permissons to $LOCKFILE ?" >&2
            return 1
        fi
    fi
}

# Removes the lock file only if this script created it ($ACQUIRED is set), 
# OR, if we are removing a stale lock file (first parameter is "FORCE") 
release () {
    #Destroy lock file. Take no prisoners.
    if [ "$ACQUIRED" ] || [ "$1" == "FORCE" ]; then
        rm -f $LOCKFILE
    fi
}

# Test code
# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if [ $? -eq 0 ]; then 
    echo "Acquired lock."
    read -p "Press [Enter] key to release lock..."
    release
    echo "Released lock."
else
    echo "Unable to acquire lock."
fi
于 2013-12-31T19:35:52.993 に答える
3

一部の UNIXlockfileには、前述のflock.

マンページから:

lockfile を使用して、1 つ以上のセマフォ ファイルを作成できます。lock-file が指定されたすべてのファイルを (指定された順序で) 作成できない場合、sleeptime (デフォルトは 8) 秒待機し、最後に失敗したファイルを再試行します。失敗が返されるまでの再試行回数を指定できます。再試行回数が -1 (デフォルト、つまり -r-1) の場合、lockfile は永久に再試行します。

于 2008-10-09T13:44:26.243 に答える
2

実際、bmdhacksの答えはほぼ良いですが、最初にロックファイルをチェックした後、それを書き込む前に2番目のスクリプトが実行される可能性がわずかにあります。したがって、両方ともロックファイルを書き込み、両方とも実行されます。確実に機能させる方法は次のとおりです。

lockfile=/var/lock/myscript.lock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null ; then
  trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else
  # or you can decide to skip the "else" part if you want
  echo "Another instance is already running!"
fi

このnoclobberオプションは、ファイルがすでに存在する場合にリダイレクトコマンドが失敗することを確認します。したがって、リダイレクトコマンドは実際にはアトミックです。1つのコマンドでファイルを書き込んで確認します。ファイルの最後にあるロックファイルを削除する必要はありません。トラップによって削除されます。これが後で読む人に役立つことを願っています。

PSたとえば、Ctrl-Cでスクリプトを停止した後にロックファイルが残る可能性を減らすためのトラップコマンドが含まれていなかったにもかかわらず、Mikelがすでに質問に正しく答えていることはわかりませんでした。これが完全なソリューションです

于 2012-10-15T09:05:18.060 に答える
1

セマフォリックユーティリティは、flock(上記で説明したように、たとえば presto8 によって) を使用して、カウント セマフォを実装します。必要な特定の数の同時プロセスを有効にします。これを使用して、さまざまなキュー ワーカー プロセスの同時実行レベルを制限します。

semに似ていますが、はるかに軽量です。(完全な開示: sem が私たちのニーズに対して重すぎることがわかり、単純なカウント セマフォ ユーティリティが利用できないことがわかった後に書きました。)

于 2015-03-18T13:23:36.563 に答える
1

少なくとも私のユースケースでは、bmdhack のソリューションが最も実用的であることがわかりました。flock と lockfile の使用は、スクリプトの終了時に rm を使用してロックファイルを削除することに依存しており、これは常に保証されるわけではありません (たとえば、kill -9)。

bmdhack の解決策について 1 つの小さな点を変更します。このセマフォを安全に動作させるために不要であるとは述べずに、ロック ファイルを削除することを強調します。彼の kill -0 の使用により、デッドプロセスの古いロックファイルが単純に無視/上書きされることが保証されます。

したがって、私の簡単な解決策は、シングルトンの先頭に次を追加することです。

## Test the lock
LOCKFILE=/tmp/singleton.lock 
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "Script already running. bye!"
    exit 
fi

## Set the lock 
echo $$ > ${LOCKFILE}

もちろん、このスクリプトには、同時に開始される可能性が高いプロセスが競合するという欠陥があります。ロック テストと設定操作は単一のアトミック アクションではないためです。しかし、mkdir を使用するために lhunath によって提案された解決策には、強制終了されたスクリプトがディレクトリに残り、他のインスタンスが実行できなくなるという欠陥があります。

于 2011-01-14T08:46:30.480 に答える
1

PID とロックファイルは間違いなく最も信頼できるものです。プログラムを実行しようとすると、ロックファイルが存在するかどうかを確認できます。存在するps場合は、プロセスがまだ実行されているかどうかを確認するために使用できます。そうでない場合は、スクリプトを開始して、ロックファイル内の PID を独自のものに更新できます。

于 2008-10-09T00:20:07.393 に答える
1

プロセスのロックを使用すると、はるかに強力になり、不正な終了も処理されます。プロセスが実行されている限り、lock_file は開いたままになります。プロセスが存在すると (プロセスが強制終了された場合でも)、(シェルによって) 閉じられます。これは非常に効率的であることがわかりました:

lock_file=/tmp/`basename $0`.lock

if fuser $lock_file > /dev/null 2>&1; then
    echo "WARNING: Other instance of $(basename $0) running."
    exit 1
fi
exec 3> $lock_file 
于 2019-01-23T06:54:20.550 に答える
0

上記の回答を組み合わせた、より洗練された、フェイルセーフで、迅速で汚れた方法を次に示します。

使用法

  1. sh_lock_functions.sh を含める
  2. sh_lock_initを使用した初期化
  3. sh_acquire_lockを使用してロックする
  4. sh_check_lockを使用してロックをチェックする
  5. sh_remove_lockを使用してロックを解除する

スクリプト ファイル

sh_lock_functions.sh

#!/bin/bash

function sh_lock_init {
    sh_lock_scriptName=$(basename $0)
    sh_lock_dir="/tmp/${sh_lock_scriptName}.lock" #lock directory
    sh_lock_file="${sh_lock_dir}/lockPid.txt" #lock file
}

function sh_acquire_lock {
    if mkdir $sh_lock_dir 2>/dev/null; then #check for lock
        echo "$sh_lock_scriptName lock acquired successfully.">&2
        touch $sh_lock_file
        echo $$ > $sh_lock_file # set current pid in lockFile
        return 0
    else
        touch $sh_lock_file
        read sh_lock_lastPID < $sh_lock_file
        if [ ! -z "$sh_lock_lastPID" -a -d /proc/$sh_lock_lastPID ]; then # if lastPID is not null and a process with that pid exists
            echo "$sh_lock_scriptName is already running.">&2
            return 1
        else
            echo "$sh_lock_scriptName stopped during execution, reacquiring lock.">&2
            echo $$ > $sh_lock_file # set current pid in lockFile
            return 2
        fi
    fi
    return 0
}

function sh_check_lock {
    [[ ! -f $sh_lock_file ]] && echo "$sh_lock_scriptName lock file removed.">&2 && return 1
    read sh_lock_lastPID < $sh_lock_file
    [[ $sh_lock_lastPID -ne $$ ]] && echo "$sh_lock_scriptName lock file pid has changed.">&2  && return 2
    echo "$sh_lock_scriptName lock still in place.">&2
    return 0
}

function sh_remove_lock {
    rm -r $sh_lock_dir
}

使用例

sh_lock_usage_example.sh

#!/bin/bash
. /path/to/sh_lock_functions.sh # load sh lock functions

sh_lock_init || exit $?

sh_acquire_lock
lockStatus=$?
[[ $lockStatus -eq 1 ]] && exit $lockStatus
[[ $lockStatus -eq 2 ]] && echo "lock is set, do some resume from crash procedures";

#monitoring example
cnt=0
while sh_check_lock # loop while lock is in place
do
    echo "$sh_scriptName running (pid $$)"
    sleep 1
    let cnt++
    [[ $cnt -gt 5 ]] && break
done

#remove lock when process finished
sh_remove_lock || exit $?

exit 0

特徴

  • ファイル、ディレクトリ、およびプロセス ID の組み合わせを使用してロックし、プロセスがまだ実行されていないことを確認します
  • ロック解除前にスクリプトが停止したかどうかを検出できます (例: プロセスの強制終了、シャットダウン、エラーなど)。
  • ロックファイルを確認し、ロックが見つからない場合にプロセスのシャットダウンをトリガーするために使用できます
  • 詳細なエラー メッセージを出力して、デバッグを容易にします。
于 2014-05-13T07:56:52.410 に答える
0

群れの道は行く道です。スクリプトが突然死んだらどうなるか考えてみてください。群れの場合、群れをほぐすだけですが、それは問題ではありません。また、邪悪なトリックはスクリプト自体に群がることであることに注意してください..しかし、もちろん、それはパーミッションの問題に全速力で実行することを可能にします.

于 2008-10-04T08:58:29.520 に答える
0
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
    exit 1
fi
于 2015-11-30T13:30:01.020 に答える
0

速くて汚い?

#!/bin/sh

if [ -f sometempfile ]
  echo "Already running... will now terminate."
  exit
else
  touch sometempfile
fi

..do what you want here..

rm sometempfile
于 2008-10-09T00:19:53.527 に答える
0

この1行の回答は、 Ask Ubuntu Q&Aに関連する誰かからのものです。

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
#     This is useful boilerplate code for shell scripts.  Put it at the top  of
#     the  shell script you want to lock and it'll automatically lock itself on
#     the first run.  If the env var $FLOCKER is not set to  the  shell  script
#     that  is being run, then execute flock and grab an exclusive non-blocking
#     lock (using the script itself as the lock file) before re-execing  itself
#     with  the right arguments.  It also sets the FLOCKER env var to the right
#     value so it doesn't run again.
于 2018-03-17T15:54:09.400 に答える
-1

スクリプト名が一意の場合、これは機能します。

#!/bin/bash
if [ $(pgrep -c $(basename $0)) -gt 1 ]; then 
  echo $(basename $0) is already running
  exit 0
fi

スクリプト名が一意でない場合、これはほとんどの Linux ディストリビューションで機能します。

#!/bin/bash
exec 9>/tmp/my_lock_file
if ! flock -n 9  ; then
   echo "another instance of this script is already running";
   exit 1
fi

ソース: http://mywiki.wooledge.org/BashFAQ/045

于 2016-09-22T21:33:55.047 に答える