Development Tip

한 번에 하나의 셸 스크립트 인스턴스 만 실행되도록하는 빠르고 간단한 방법

yourdevel 2020. 10. 30. 21:07
반응형

한 번에 하나의 셸 스크립트 인스턴스 만 실행되도록하는 빠르고 간단한 방법


주어진 시간에 하나의 쉘 스크립트 인스턴스 만 실행되도록하는 빠르고 더러운 방법은 무엇입니까?


다음은 잠금 파일 을 사용하고 여기 에 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).


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

코드 사이의이 보장하지만 (하고 )한 번에 하나 개의 프로세스 만이 실행되는 프로세스가 너무 오래 잠금을 대기하지 않습니다.

주의 사항 :이 특정 명령은 util-linux. Linux 이외의 운영 체제를 실행하는 경우 사용 가능할 수도 있고 사용하지 못할 수도 있습니다.


"잠금 파일"의 존재를 테스트하는 모든 접근 방식에는 결함이 있습니다.

왜? 파일이 존재하는지 여부를 확인하고 단일 원자 작업으로 생성 할 수있는 방법이 없기 때문입니다. 이것 때문에; 경쟁 조건이 것입니다 상호 배제 휴식에 당신의 시도를이.

대신 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)도 있습니다.


flock (2) 시스템 호출 주위에는 상상할 수 없을 정도로 flock (1)이라는 래퍼가 있습니다. 이렇게하면 정리 등에 대한 걱정없이 배타적 잠금을 비교적 안정적으로 얻을 수 있습니다 . 셸 스크립트에서 사용하는 방법 대한 예제 가 man 페이지 에 있습니다.


무리와 같은 원자 적 연산이 필요합니다. 그렇지 않으면 결국 실패합니다.

그러나 무리를 사용할 수없는 경우해야 할 일. mkdir이 있습니다. 그것도 원자 적 연산입니다. 하나의 프로세스 만 mkdir에 성공하고 다른 모든 프로세스는 실패합니다.

따라서 코드는 다음과 같습니다.

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

크래시가 발생하면 스크립트가 다시 실행되지 않을 경우 오래된 잠금을 처리해야합니다.


잠금을 안정적으로 만들려면 원자 적 작업이 필요합니다. 위의 제안 중 다수는 원 자성이 아닙니다. 제안 된 lockfile (1) 유틸리티는 맨 페이지에서 언급 한 것처럼 "NFS 내성"이라는 유망한 것처럼 보입니다. OS가 lockfile (1)을 지원하지 않고 솔루션이 NFS에서 작동해야하는 경우 옵션이 많지 않습니다 ....

NFSv2에는 두 가지 원자 적 작업이 있습니다.

  • 심볼릭 링크
  • 이름 바꾸기

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

Remove도 원 자성이 아니므로 먼저 이름을 바꾸고 (atomic) 제거합니다.

symlink 및 rename 호출의 경우 두 파일 이름이 동일한 파일 시스템에 있어야합니다. 내 제안 : 단순한 파일 이름 (경로 없음) 만 사용하고 파일을 넣고 동일한 디렉토리에 잠급니다.


또 다른 옵션은를 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

으로 GNU Parallel호출 될 때 뮤텍스로 작동하므로이를 위해 사용할 수 있습니다 sem. 따라서 구체적인 용어로 다음을 사용할 수 있습니다.

sem --id SCRIPTSINGLETON yourScript

시간 제한도 원한다면 다음을 사용하십시오.

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

시간 초과 <0은 세마포어가 시간 초과 내에 해제되지 않으면 스크립트를 실행하지 않고 종료 함을 의미하고, 시간 초과가 0보다 크면 스크립트가 실행됨을 의미합니다.

이름 (와 함께 --id)을 지정해야합니다. 그렇지 않으면 기본적으로 제어 터미널이 사용됩니다.

GNU Parallel 대부분의 Linux / OSX / Unix 플랫폼에서 매우 간단하게 설치할 수 있으며 Perl 스크립트 일뿐입니다.


쉘 스크립트의 경우 잠금 장치를 더 이식 가능하게 만들기 때문에 mkdir오버 하는 경향이 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까지의 숫자를 만들거나 기대합니다. 다른 것으로 설정하면 스크립트와 일괄 스트림이 이전 일괄 작업 또는 스크립트에 따라 반응하도록 할 수 있습니다.


정말 빠르고 정말 더럽습니까? 스크립트 맨 위에있는이 한 줄짜리가 작동합니다.

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

물론 스크립트 이름이 고유한지 확인하십시오. :)


알려진 위치에 잠금 파일을 만들고 스크립트 시작시 존재하는지 확인 하시겠습니까? 누군가가 스크립트 실행을 방해하는 잘못된 인스턴스를 추적하려는 경우 파일에 PID를 넣으면 도움이 될 수 있습니다.


다음은 원자 디렉토리 잠금과 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

이 예제는 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 for remove parasite nad 최종적으로 grep -c로 계산해야합니다. 숫자와 비교합니다. 복잡하고 불확실한


데비안 머신을 대상으로 할 때 lockfile-progs패키지가 좋은 솔루션이라는 것을 알았습니다 . 도구 procmail도 함께 제공됩니다 lockfile. 그러나 때때로 나는 이것들 중 어느 것도 붙어 있지 않습니다.

다음 mkdir은 원 자성 및 PID 파일을 사용하여 오래된 잠금을 감지하는 내 솔루션입니다 . 이 코드는 현재 Cygwin 설정에서 생산 중이며 잘 작동합니다.

그것을 사용하려면 exclusive_lock_require무언가에 대한 독점적 인 액세스가 필요할 때 전화하십시오 . 선택적 잠금 이름 매개 변수를 사용하면 서로 다른 스크립트간에 잠금을 공유 할 수 있습니다. 더 복잡한 것이 필요한 경우 두 개의 하위 수준 함수 ( exclusive_lock_tryexclusive_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
}

이 스레드의 다른 곳에서 이미 설명 된 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 

일부 유닉스에는 lockfile이미 언급 한 것과 매우 유사한 기능이 flock있습니다.

맨 페이지에서 :

lockfile은 하나 이상의 세마포어 파일을 만드는 데 사용할 수 있습니다. lock-file이 지정된 모든 파일 (지정된 순서대로)을 생성 할 수없는 경우, sleeptime (기본값은 8) 초 동안 대기하고 성공하지 못한 마지막 파일을 재 시도합니다. 실패가 반환 될 때까지 수행 할 재시도 횟수를 지정할 수 있습니다. 재시도 횟수가 -1 (기본값, 즉 -r-1)이면 잠금 파일이 영원히 재 시도합니다.


나는 lockfiles, lockdirs, 특별한 잠금 프로그램을 없애고 싶었고 심지어 pidof모든 Linux 설치에서 찾을 수는 없었습니다. 또한 가능한 한 가장 간단한 코드 (또는 최소한 가능한 한 적은 줄)를 원했습니다. if한 줄로 된 가장 간단한 문장 :

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

게시 된 기존 답변은 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 환경에서 모두 사용했습니다.


실제로 bmdhacks의 대답은 거의 좋지만 두 번째 스크립트가 먼저 잠금 파일을 확인한 후 작성하기 전에 실행될 가능성이 약간 있습니다. 따라서 둘 다 잠금 파일을 작성하고 둘 다 실행됩니다. 확실히 작동하는 방법은 다음과 같습니다.

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옵션은 파일이 이미있는 경우 리디렉션 명령이 실패하도록합니다. 따라서 리디렉션 명령은 실제로 원자 적입니다. 하나의 명령으로 파일을 작성하고 확인합니다. 파일 끝에서 잠금 파일을 제거 할 필요가 없습니다. 트랩에 의해 제거됩니다. 나중에 읽을 사람들에게 도움이되기를 바랍니다.

추신 : Mikel이 예를 들어 Ctrl-C를 사용하여 스크립트를 중지 한 후 잠금 파일이 남아있을 가능성을 줄이기 위해 trap 명령을 포함하지 않았지만 Mikel이 이미 질문에 올바르게 대답 한 것을 보지 못했습니다. 그래서 이것은 완전한 솔루션입니다


오래된 잠금 파일을 처리하는 간단한 접근 방식을 사용합니다.

pid를 저장하는 위의 솔루션 중 일부는 pid가 래핑 될 수 있다는 사실을 무시합니다. 따라서 저장된 pid로 유효한 프로세스가 있는지 확인하는 것만으로는 충분하지 않습니다. 특히 장기 실행 스크립트의 경우에는 더욱 그렇습니다.

저는 noclobber를 사용하여 한 번에 하나의 스크립트 만 열고 잠금 파일에 쓸 수 있는지 확인합니다. 또한 잠금 파일에서 프로세스를 고유하게 식별하기에 충분한 정보를 저장합니다. 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

스크립트 시작 부분에이 줄을 추가하십시오.

[ "${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합니다. 성공했고 계속해도 괜찮습니다. 잠금이 사용 중이면 구성 가능한 종료 코드로 실패합니다.

데비안 7에서 작동하지 않는 것 같지만 실험적인 util-linux 2.25 패키지로 다시 작동하는 것 같습니다. "flock : ... Text file busy"라고 기록됩니다. 스크립트에 대한 쓰기 권한을 비활성화하여 재정의 할 수 있습니다.


PID와 잠금 파일은 확실히 가장 신뢰할 수 있습니다. 프로그램을 실행하려고 할 때 잠금 파일이 ps있는지 확인하여 프로세스가 아직 실행 중인지 확인하는 데 사용할 수 있습니다 . 그렇지 않은 경우 스크립트가 시작되어 잠금 파일의 PID를 자체로 업데이트 할 수 있습니다.


적어도 내 사용 사례에서는 bmdhack의 솔루션이 가장 실용적이라는 것을 알았습니다. flock 및 lockfile을 사용하려면 스크립트가 종료 될 때 rm을 사용하여 잠금 파일을 제거해야합니다. 이는 항상 보장 할 수 없습니다 (예 : kill -9).

나는 bmdhack의 솔루션에 대해 한 가지 사소한 점을 변경할 것입니다.이 세마포어의 안전한 작동을 위해 이것이 불필요하다고 언급하지 않고 잠금 파일을 제거하는 지점을 만듭니다. 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}

물론이 스크립트에는 잠금 테스트와 집합 작업이 단일 원자 적 작업이 아니기 때문에 동시에 시작될 가능성이있는 프로세스가 경쟁 위험이 있다는 결함이 여전히 있습니다. 그러나 lhunath가 mkdir을 사용하기 위해 제안한 솔루션에는 죽은 스크립트가 디렉토리 뒤에 남겨져 다른 인스턴스가 실행되지 못하게 할 수있는 결함이 있습니다.


semaphoric 실용 용도 flock(AS presto8 의해 예 상술)을 구현하는 카운팅 세마포어 . 원하는 특정 수의 동시 프로세스를 활성화합니다. 다양한 큐 작업자 프로세스의 동시성 수준을 제한하는 데 사용합니다.

It's like sem but much lighter-weight. (Full disclosure: I wrote it after finding the sem was way too heavy for our needs and there wasn't a simple counting semaphore utility available.)


An example with flock(1) but without subshell. flock()ed file /tmp/foo is never removed, but that doesn't matter as it gets flock() and 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

Answered a million times already, but another way, without the need for external dependencies:

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

Each time it writes the current PID ($$) into the lockfile and on script startup checks if a process is running with the latest PID.


Using the process's lock is much stronger and takes care of the ungraceful exits also. lock_file is kept open as long as the process is running. It will be closed (by shell) once the process exists (even if it gets killed). I found this to be very efficient:

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 

The flock path is the way to go. Think about what happens when the script suddenly dies. In the flock-case you just loose the flock, but that is not a problem. Also, note that an evil trick is to take a flock on the script itself .. but that of course lets you run full-steam-ahead into permission problems.


Quick and dirty?

#!/bin/sh

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

..do what you want here..

rm sometempfile

Take a look to FLOM (Free LOck Manager) http://sourceforge.net/projects/flom/: you can synchronize commands and/or scripts using abstract resources that does not need lock files in a filesystem. You can synchronize commands running in different systems without a NAS (Network Attached Storage) like an NFS (Network File System) server.

Using the simplest use case, serializing "command1" and "command2" may be as easy as executing:

flom -- command1

and

flom -- command2

from two different shell scripts.

참고URL : https://stackoverflow.com/questions/169964/how-to-prevent-a-script-from-running-simultaneously

반응형