web-dev-qa-db-ja.com

シェルスクリプトのインスタンスが一度に1つだけ実行されるようにするための迅速で汚れた方法

シェルスクリプトの1つのインスタンスのみが特定の時間に実行されていることを確認するための迅速で汚い方法は何ですか?

171
raldi

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以外のオペレーティングシステムを実行している場合、使用できる場合と使用できない場合があります。

198
Alex B

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

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

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

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)もあります

152
lhunath

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

42
Cowan

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

しかし、群れが利用できない場合の対処方法。まあmkdirがあります。それも原子操作です。 mkdirが成功するプロセスは1つだけで、他のプロセスはすべて失敗します。

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

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

古いロックの世話をする必要があります。そうしないと、スクリプトが二度と実行されないクラッシュが発生します。

27
Gunstick

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

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

  • シンボリックリンク
  • リネーム

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

NFSv2およびNFSv3では、ディレクトリ操作はアトミックではありません(Brent Callaghan著の書籍「NFS Illustrated」、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

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

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

24
Stefan Tramm

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

簡単に言うと:

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

興味深いことにpdkshO_TRUNCフラグを追加しますが、明らかに冗長です:
空のファイルを作成しているか、何もしていません。


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

クリーン終了時に削除

不正な出口を引き​​起こした問題が解決され、ロックファイルが手動で削除されるまで、新しい実行は失敗します。

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

任意の出口で削除

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

trap 'rm "$lockfile"' EXIT
22
Mikel

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

sem --id SCRIPTSINGLETON yourScript

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

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

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

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

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

18
Mark Setchell

シェルスクリプトの場合、ロックの移植性を高めるため、mkdir over 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までの数字を期待します。これらを他の値に設定すると、スクリプトとバッチストリームを前のバッチジョブまたはスクリプトに応じて反応させることができます。

16
Mark Stinson

本当に 迅速かつ 本当に 汚れた?スクリプトの一番上にあるこの1行は機能します。

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

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

13
Majal

この例はman flockで説明されていますが、バグと終了コードを管理する必要があるため、多少の改善が必要です。

   #!/bin/bash
   #set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.

( #start subprocess
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200
  if [ "$?" != "0" ]; then echo Cannot lock!; exit 1; fi
  echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom  ) 200>/var/lock/.myscript.exclusivelock.
  # Do stuff
  # you can properly manage exit codes with multiple command and process algorithm.
  # I suggest throw this all to external procedure than can properly handle exit X commands

) 200>/var/lock/.myscript.exclusivelock   #exit subprocess

FLOCKEXIT=$?  #save exitcode status
    #do some finish commands

exit $FLOCKEXIT   #return properly exitcode, may be usefull inside external scripts

別の方法を使用して、過去に使用したプロセスをリストすることもできます。しかし、これは上記の方法よりも複雑です。プロセスをpsでリストし、その名前でフィルター処理し、追加のフィルターgrep -v grepで寄生虫を除去し、最後にgrep -cでカウントします。数値と比較します。その複雑で不確実な

5
Znik

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

5
Rob

アトミックディレクトリロックと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
5
bk138

このスレッドの他の場所ですでに説明されている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 
4
presto8

Debianマシンをターゲットにする場合、lockfile-progsパッケージが適切なソリューションであることがわかります。 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
}
4
Jason Weathered

すべてのLinuxインストールで見つかるわけではないので、ロックファイル、ロックディレクトリ、特別なロックプログラム、さらにはpidofを廃止したかったのです。また、可能な限りシンプルなコード(または、少なくとも可能な限り少ない行)が必要でした。最も単純なifステートメント、1行で:

if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
3
linux_newbie

投稿された既存の回答は、CLIユーティリティflockに依存しているか、ロックファイルを適切に保護していません。 flockユーティリティは、すべての非Linuxシステム(つまりFreeBSD)で利用できるわけではなく、NFSでは正常に動作しません。

システム管理とシステム開発の初期の頃、ロックファイルを作成する安全で比較的移植性の高い方法は、mkemp(3)またはmkemp(1)を使用して一時ファイルを作成し、一時ファイル(PIDなど)、一時ファイルをロックファイルにハードリンクします。リンクが成功した場合、ロックは正常に取得されています。

シェルスクリプトでロックを使用する場合、通常、共有プロファイルにobtain_lock()関数を配置し、スクリプトからソースを取得します。以下は私のロック機能の例です:

obtain_lock()
{
  LOCK="${1}"
  LOCKDIR="$(dirname "${LOCK}")"
  LOCKFILE="$(basename "${LOCK}")"

  # create temp lock file
  TMPLOCK=$(mktemp -p "${LOCKDIR}" "${LOCKFILE}XXXXXX" 2> /dev/null)
  if test "x${TMPLOCK}" == "x";then
     echo "unable to create temporary file with mktemp" 1>&2
     return 1
  fi
  echo "$$" > "${TMPLOCK}"

  # attempt to obtain lock file
  ln "${TMPLOCK}" "${LOCK}" 2> /dev/null
  if test $? -ne 0;then
     rm -f "${TMPLOCK}"
     echo "unable to obtain lockfile" 1>&2
     if test -f "${LOCK}";then
        echo "current lock information held by: $(cat "${LOCK}")" 1>&2
     fi
     return 2
  fi
  rm -f "${TMPLOCK}"

  return 0;
};

以下は、ロック機能の使用方法の例です。

#!/bin/sh

. /path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"

clean_up()
{
  rm -f "${PROG_LOCKFILE}"
}

obtain_lock "${PROG_LOCKFILE}"
if test $? -ne 0;then
   exit 1
fi
trap clean_up SIGHUP SIGINT SIGTERM

# bulk of script

clean_up
exit 0
# end of script

スクリプトの出口ポイントでclean_upを呼び出すことを忘れないでください。

LinuxとFreeBSDの両方の環境で上記を使用しました。

3
David M. Syzdek

一部のUNIXにはlockfileがあります。これは、前述のflockと非常に似ています。

マンページから:

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

3
dmckee

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

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

これは、男の群れからの定型コードです。

より多くのロギングが必要な場合は、これを使用してください

[ "${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変数が初期化された状態で再帰的にスクリプトを再起動しようとします。成功し、続行しても問題ありません。ロックがビジーの場合、構成可能な終了コードで失敗します。

Debian 7では動作しないようですが、実験的なutil-linux 2.25パッケージで再び動作するようです。 「flock:...テキストファイルがビジー」と書き込みます。スクリプトの書き込み許可を無効にすることで上書きできます。

2
user3132194

実際、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つのコマンドでファイルを記述および確認します。ファイルの最後でロックファイルを削除する必要はありません-トラップによって削除されます。これが後で読む人々に役立つことを願っています。

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

2
NickSoft

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

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
2
rouble

Flock(1)を使用するがサブシェルを使用しない例。 flock()edファイル/ tmp/fooが削除されることはありませんが、flock()およびun-flock()edを取得するので問題ありません。

#!/bin/bash

exec 9<> /tmp/foo
flock -n 9
RET=$?
if [[ $RET -ne 0 ]] ; then
    echo "lock failed, exiting"
    exit
fi

#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&- #close fd 9, and release lock

#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5
1
sivann

プロセスのロックを使用すると、より強力になり、不正な終了も処理します。プロセスが実行されている限り、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 
1
Sudhir Kumar

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

1
Drew Stephens

少なくとも私のユースケースでは、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が提案したこのソリューションには、削除されたスクリプトがディレクトリに残る可能性があるため、他のインスタンスが実行されないという欠陥があります。

1
thecowster

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

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

1
Tim Bunce

すでに100万回答えましたが、外部の依存関係を必要としない別の方法:

LOCK_FILE="/var/lock/$(basename "$0").pid"
trap "rm -f ${LOCK_FILE}; exit" INT TERM EXIT
if [[ -f $LOCK_FILE && -d /proc/`cat $LOCK_FILE` ]]; then
   // Process already exists
   exit 1
fi
echo $$ > $LOCK_FILE

現在のPID($$)をロックファイルに書き込むたびに、スクリプトの起動時に、プロセスが最新のPIDで実行されているかどうかをチェックします。

1
Filidor Wiese

これはどこにも言及されていませんが、読み取りを使用します。読み取りが実際にアトミックであるかどうかは正確にはわかりませんが、これまで私に役立ってきました...実装、ロッカーコプロセスを開始し、そのI/Oを使用してロックを管理します。ターゲットI/Oをロッカーファイル記述子からファイルシステムファイル記述子(exec 3<>/file && exec 4</file)にスワップするだけで、プロセス間で同じことができます。

## gives locks
locker() {
    locked=false
    while read l; do
        case "$l" in
            lock)
                if $locked; then
                    echo false
                else
                    locked=true
                    echo true
                fi
                ;;
            unlock)
                if $locked; then
                    locked=false
                    echo true
                else
                    echo false
                fi
                ;;
            *)
                echo false
                ;;
        esac
    done
}
## locks
lock() {
    local response
    echo lock >&${locker[1]}
    read -ru ${locker[0]} response
    $response && return 0 || return 1
}

## unlocks
unlock() {
    local response
    echo unlock >&${locker[1]}
    read -ru ${locker[0]} response
    $response && return 0 || return 1
}
0
untore

群れの道は行く道です。スクリプトが突然死んだときに何が起こるか考えてください。群れの場合、群れを放すだけですが、それは問題ではありません。また、悪意のあるトリックは、スクリプト自体に群れをとることであることに注意してください。

0

なぜ私たちは次のようなものを使用しないのですか

pgrep -f $cmd || $cmd
0
Jabir Ahmed

早くて汚い?

#!/bin/sh

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

..do what you want here..

rm sometempfile
0
Aupajo

FLOM(Free LOck Manager)をご覧ください http://sourceforge.net/projects/flom/ :コマンドやスクリプトを同期させることができますファイルシステム内のロックファイルを必要としない抽象的なリソース。 NFS(ネットワークファイルシステム)サーバーのようなNAS(ネットワーク接続ストレージ)を使用せずに、異なるシステムで実行されているコマンドを同期できます。

最も単純なユースケースを使用すると、「command1」と「command2」のシリアル化は実行と同じくらい簡単です。

flom -- command1

そして

flom -- command2

2つの異なるシェルスクリプトから。

0
tiian
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
    exit 1
fi
0
Rudolf Lörcks

この1行の答えは、関連する誰かから来ています buntu 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.
0

ファイル名に基づいた簡単な解決策があります

#!/bin/bash

MY_FILENAME=`basename "$BASH_SOURCE"`

MY_PROCESS_COUNT=$(ps a -o pid,cmd | grep $MY_FILENAME | grep -v grep | grep -v $$ | wc -
l)

if [ $MY_PROCESS_COUNT -ne 0  ]; then
  echo found another process
  exit 0
if

# Follows the code to get the job done.
0
Gianluca Casati

これは、よりエレガントで、フェイルセーフ、迅速です & 汚れた 上記の回答を組み合わせた方法。

使用法

  1. include sh_lock_functions.sh
  2. sh_lock_initを使用した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の組み合わせを使用してロックし、プロセスがまだ実行されていないことを確認します
  • ロックを解除する前にスクリプトが停止したかどうかを検出できます(プロセスの強制終了、シャットダウン、エラーなど)。
  • ロックファイルを確認し、それを使用して、ロックが失われたときにプロセスのシャットダウンをトリガーできます。
  • 冗長、デバッグを容易にするためのエラーメッセージを出力します
0
Stefan Rogin

これも使用できます: https://github.com/sayanarijit/pidlock

Sudo pip install -U pidlock

pidlock -n sleepy_script -c 'sleep 10'
0
Arijit Basu

パーティーに遅れて、@ Majalのアイデアを使用して、これはemacsclient GUIの1つのインスタンスのみを開始する私のスクリプトです。これを使用して、同じemacsclientを開くか、ジャンプするようにショートカットキーを設定できます。必要なときに端末でemacsclientを呼び出す別のスクリプトがあります。ここでemacsclientを使用するのは、実際の例を示すためだけであり、他のものを選択できます。このアプローチは、私の小さなスクリプトには十分迅速かつ適切です。汚れている場所を教えてください:)

#!/bin/bash

# if [ $(pgrep -c $(basename $0)) -lt 2 ]; then # this works but requires script name to be unique
if [ $(pidof -x "$0"|wc -w ) -lt 3 ]; then
    echo -e "Starting $(basename $0)"
    emacsclient --alternate-editor="" -c "$@"
else
    echo -e "$0 is running already"
fi
0
biocyberman