programing

셸 스크립트의 인스턴스를 한 번에 하나만 실행할 수 있는 빠르고 더러운 방법

starjava 2023. 4. 17. 21:14
반응형

셸 스크립트의 인스턴스를 한 번에 하나만 실행할 수 있는 빠르고 더러운 방법

셸 스크립트의 인스턴스가 한 번에 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

이것에 의해, 다음의 코드간의 코드가 확실히 설정됩니다.( ★★★★★★★★★★★★★★★★★」)한 번에 하나의 프로세스로만 실행되며 프로세스가 잠길 때까지 너무 오래 기다리지 않습니다.

주의: 이 명령어는 의 일부입니다.Linux 이외의 운영체제를 실행하고 있는 경우 사용할 수 없는 경우가 있습니다.

"잠금 파일"의 존재를 테스트하는 순진한 접근법에는 결함이 있습니다.

왜냐고요? 파일이 존재하는지 확인하고 단일 원자 작용으로 파일을 생성하지 않기 때문입니다.이것 때문에; 상호 배타적 관계를 끊으려는 시도를 하게 만드는 인종 조건이 있습니다.

신을 사용할 수 .mkdirmkdir디렉토리가 아직 존재하지 않는 경우는 디렉토리를 작성하고, 존재하지 않는 경우는 종료 코드를 설정합니다.더 중요한 것은 이 모든 것을 단일 원자 작용으로 수행하므로 이 시나리오에 완벽하게 대응한다는 것입니다.

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

상세한 것에 대하여는, 뛰어난 Bash를 참조해 주세요.FAQ: 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)도 있습니다.

다음은 잠금 파일을 사용하여 PID를 에코하는 구현입니다.이는 pid 파일을 삭제하기 전에 프로세스가 종료될 경우 보호 역할을 합니다.

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}

은 여기 the the the이다.kill -0신호를 전달하지 않고 지정된 PID를 가진 프로세스가 존재하는지 여부만 확인합니다., ,, 의, 에, 에, 에, 습, also, also, also, also, also, also, also,trap프로세스가 종료된 경우에도 록파일이 확실하게 삭제됩니다(제외).kill -9를 참조해 주세요.

flock(2) 시스템 호출 주위에는 상상할 수 없을 정도로 flock(1)이라고 불리는 래퍼가 있습니다.이것에 의해, 청소등의 염려 없이, 전용의 잠금을 확실히 취득할 수 있습니다. 스크립트에서 사용하는 방법에 대한 예는 man 페이지에 있습니다.

잠금을 신뢰할 수 있게 하려면 원자 수술이 필요합니다.위의 제안들 중 많은 것들이 원자적이지 않다.제안된 lockfile(1) 유틸리티는 man-page에서 언급했듯이 "NFS 내구성"이 우수해 보입니다.OS가 lockfile(1)을 지원하지 않고 솔루션이 NFS에서 작동해야 하는 경우 옵션이 많지 않습니다.

NFSv2에는 다음 두 가지 원자 연산이 있습니다.

  • 심볼링크
  • 이름을 바꾸다

NFSv3에서는 작성 콜도 atomic입니다.

NFSv2 및 NFSv3에서는 디렉터리 작업이 원자적이지 않습니다(Brent Callaghan, ISBN 0-201-32570-5의 'NFS Illustated' 책을 참조하십시오. Brent는 Sun의 NFS-베테란입니다).

이것을 알고 있으면, 파일이나 디렉토리(PHP가 아닌 셸)의 스핀 잠금을 실장할 수 있습니다.

전류 dir 잠금:

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

파일 잠금:

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

unlock current dir(소비, 실행 중인 프로세스가 실제로 잠금을 획득):

mv lock deleteme && rm deleteme

파일 잠금 해제(복제, 실행 중인 프로세스가 실제로 잠금을 획득):

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

Remove도 atomic이 아니기 때문에 먼저 이름 변경(atomic) 후 remove를 수행합니다.

Symblink 콜과 이름 변경 콜의 경우 두 파일 이름이 동일한 파일 시스템에 있어야 합니다.제안: 단순한 파일 이름(경로 없음)만 사용하고 파일과 잠금을 동일한 디렉토리에 넣습니다.

무리와 같은 원자 작전이 필요해 그렇지 않으면 결국 실패하게 될 거야.

하지만 무리를 구할 수 없다면 어떻게 해야 할까.음, mkdir가 있습니다.그것도 원자폭탄 작전이야mkdir가 성공하는 프로세스는 1개뿐이며, 다른 프로세스는 모두 실패합니다.

코드는 다음과 같습니다.

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

오래된 잠금을 처리해야 합니다.그렇지 않으면 크래시 후 스크립트가 다시 실행되지 않습니다.

다른 조개껍데기를 하는 방법도 있습니다.noclobber 'sunning" (옵션)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)

자동으로 파일을 생성하거나 파일이 이미 존재하는 경우 실패합니다.


Bash 댓글에 따르면FAQ 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 ParallelLinux/OSX/Unix는 Perl을 사용합니다.

스크립트의 , 는 「」를 이 있습니다.mkdir에 걸쳐서flock자물쇠가 휴대성이 좋아지기 때문입니다.

쪽이든 '어느 쪽이든'을 set -e충분하지 않습니다.명령어가 실패했을 경우에만 스크립트가 종료됩니다.당신의 자물쇠는 여전히 남겨져 있을 것입니다.

적절한 잠금 정리를 위해서는 실제로 트랩을 다음과 같은 psuedo 코드(리프트, 단순화 및 테스트되지 않았지만 액티브하게 사용되는 스크립트)로 설정해야 합니다.

#=======================================================================
# 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

물론 스크립트 이름이 고유해야 합니다.:)

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

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

이 예는 man proup에 설명되어 있지만 버그와 종료 코드를 관리해야 하기 때문에 몇 가지 구현이 필요합니다.

   #!/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로 카운트하여 번호와 비교해야 합니다.복잡하고 불확실하다

"CLI" 에 합니다.flock또는 잠금 파일을 적절히 보호하지 마십시오., Linux 시스템)에서 사용할 수 것은 .SD) 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와 FreeB 모두에서 위의 내용을 사용하고 있습니다.SD 환경

스크립트의 선두에 이 행을 추가합니다.

[ "${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 변수를 체크하여 처음 실행되었는지 여부를 검출하고 스크립트 이름으로 설정되어 있지 않은 경우 FLOCKER 변수를 초기화하고 FLOCKER 변수를 사용하여 스크립트를 재귀적으로 다시 시작하려고 시도합니다.FLOCKER가 올바르게 설정되어 있으면 이전 반복에 성공하여 계속 진행해도 좋습니다.잠금이 사용 중일 경우 설정 가능한 종료 코드와 함께 실패합니다.

Debian 7에서는 동작하지 않는 것처럼 보이지만, 실험적인 util-linux 2.25 패키지로 다시 동작하는 것 같습니다."flock: ..."라고 쓰여있다.텍스트 파일이 사용 중입니다.스크립트에 대한 쓰기 권한을 해제하면 덮어쓸 수 있습니다.

, 「Debian 머신」을 수 .lockfile-progs이치노 procmail,도되어 있습니다.lockfile하지만 가끔은 이것들 중 어느 것도 할 수 없을 때가 있어요

있다, 하다, 하다, 하다를 .mkdir「PID」입니다.이 코드는 현재 Cygwin 셋업으로 가동되고 있으며 정상적으로 동작합니다.

을 간단히 , 사하기위 to to to to to to to to to to to to to to to라고 합니다.exclusive_lock_require「 」 「 」 、 「 」 다른 스크립트간에 할 수 .옵션인 잠금 이름 매개 변수를 사용하면 서로 다른 스크립트 간에 잠금을 공유할 수 있습니다.두 기능이 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
}

이 스레드의 다른 곳에서 이미 설명한 바와 같이 플룹의 제한이 문제가 되지 않는 경우 다음과 같이 하십시오.

#!/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 

일부 unix에는 이미 설명한 것과 매우 유사한 기능이 있습니다.

manpage부터:

lock file을 사용하여 하나 이상의 세마포 파일을 만들 수 있습니다.잠금 파일이 지정된 파일(지정된 순서대로)을 모두 작성할 수 없는 경우, sleep time(기본값 8) 초 동안 대기했다가 실패한 마지막 파일을 다시 시도합니다.실패가 반환될 때까지 수행할 재시도 횟수를 지정할 수 있습니다.재시도 횟수가 -1(기본값: -r-1)인 경우 잠금 파일은 영원히 재시도됩니다.

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

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

파일, 장치,, 금, 금, 수, 수, 수, 지, 까지 없애고 싶었다.pidof【Linux】【Linux】【Linux】【Linux】【Linux】【Linux】【Linux】【Linux】키로 했습니다.또, 가능한 한 심플한 코드(또는 가능한 한 적은 행수)를 사용하고 싶다고 생각하고 있습니다. 심플한 simplest simplest simplestif「」, 「」:

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

실제로 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파일이 이미 존재하는 경우 redirect 명령어가 실패하는지 확인합니다.리다이렉트하다하나의 명령어로 파일을 쓰고 체크합니다.파일 끝에 있는 잠금 파일은 삭제할 필요가 없습니다.트랩에 의해 삭제됩니다.나중에 읽을 사람들에게 도움이 됐으면 좋겠어요.

추신: Mikel은 예를 들어 Ctrl+C로 스크립트를 정지한 후 lock 파일이 남아있을 가능성을 줄이기 위해 trap 명령어를 포함하지 않았지만 질문에 이미 올바르게 답변하지 못했습니다.이것이 완전한 솔루션입니다.

flock(1)은 있지만 subshell.flock()이 없는 예에서는 /tmp/foo 파일은 삭제되지 않지만 flock() 및 un-flock()이 되기 때문에 이는 문제가 되지 않습니다.

#!/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

답변은 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.

PID와 잠금 파일이 확실히 가장 신뢰성이 높습니다.이 어떤 파일이 할 수 어떤 할 수 .ps프로세스가 아직 실행 중인지 확인합니다.그렇지 않으면 스크립트를 시작하여 잠금 파일의 PID를 자체 PID로 업데이트할 수 있습니다.

적어도 제 사용 사례에서는 bmdhack의 솔루션이 가장 실용적이라는 것을 알게 되었습니다.shrow 및 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}

물론 이 스크립트에는 잠금 테스트와 설정 작업이 단일 원자 작업이 아니기 때문에 동시에 시작될 가능성이 높은 프로세스가 경합 위험을 갖는 결함이 있습니다.그러나 mkdir를 사용하기 위해 lhunath에 의해 제안된 솔루션에는 삭제된 스크립트가 디렉토리에 남아 다른 인스턴스가 실행되지 않을 수 있다는 결함이 있습니다.

세마포릭 유틸리티는flock(예: presto8에 의해) 계수 세마포를 구현한다.원하는 수의 동시 프로세스를 실행할 수 있습니다.다양한 큐 워커 프로세스의 동시성 수준을 제한하기 위해 사용합니다.

SEM과 비슷하지만 훨씬 가볍습니다. (완전 공개:sem이 너무 무거워서 사용할 수 있는 간단한 세마포 유틸리티가 없다는 것을 알고 작성했습니다.)

외부 의존관계 없이 이미 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로 실행 중인지 확인합니다.

프로세스의 잠금을 사용하는 것이 훨씬 더 강력하며 품위 없는 출구도 처리합니다.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 

스크립트 맨 처음에 oneliner를 사용합니다.

#!/bin/bash

if [[ $(pgrep -afc "$(basename "$0")") -gt "1" ]]; then echo "Another instance of "$0" has already been started!" && exit; fi
.
the_beginning_of_actual_script

메모리에서 프로세스의 존재를 확인할 수 있는 것은 좋지만(프로세스의 상태에 관계없이), 그것은 나에게 도움이 됩니다.

기존 답변에 다음과 같은 문제가 있습니다.

  • 일부 응답은 잠금 파일을 정리하려고 시도하고, 그 후 갑작스런 크래시/재부팅 등의 이유로 오래된 잠금 파일을 처리해야 합니다.IMO는 불필요하게 복잡합니다.잠긴 파일을 그대로 둡니다.
  • 파일 .$0 ★★★★★★★★★★★★★★★★★」$BASH_SOURCE ''의 를 man flock갱신 또는 편집으로 인해 스크립트가 교체되면 삭제된 파일에 대해 잠금을 유지하고 있는 다른 인스턴스가 아직 실행 중인 경우에도 다음 실행이 열리고 새 스크립트 파일에 대한 잠금이 확보됩니다.
  • 고정 파일 기술자를 사용하는 응답은 거의 없습니다.이건 이상적이지 않아요.잠금 파일을 여는 데 실패했지만 잘못 처리되어 상위 프로세스에서 상속된 관련 없는 파일 기술자에 잠금을 시도하는 등 이 동작에 의존하지 않습니다.또 다른 실패 사례로는 서드파티 바이너리용 잠금 래퍼 주입이 있습니다.서드파티 바이너리는 잠금 자체를 처리하지 않지만 고정 파일 기술자가 하위 프로세스에 전달되는 데 방해가 될 수 있습니다.
  • 이미 실행 중인 스크립트 이름에 대한 프로세스 조회를 사용하여 응답을 거부합니다.신뢰성/원자성, 출력 구문 분석, 일부 관련 기능을 수행하는 스크립트(잠금이 필요하지 않음) 등 여러 가지 이유가 있습니다.

이 답변은 다음과 같습니다.

  • flock커널이 잠금을 제공하기 때문입니다.단, 록파일은 대체되지 않고 원자적으로 작성됩니다.
  • NFS가 아닌 로컬 파일 시스템에 잠금 파일이 저장되어 있다고 가정하고 의존합니다.
  • 잠금 파일 존재 상태를 실행 중인 인스턴스에 대해 아무 의미도 없는 것으로 변경합니다.그 역할은 단순히 두 인스턴스가 동시에 동일한 이름으로 파일을 만들고 다른 인스턴스의 복사본을 대체하는 것을 방지하는 것입니다.잠금 파일은 삭제되지 않고 남겨져 재부팅 후에도 존속할 수 있습니다.는 음음 the음 、 음음 、 。flock을 사용하다
  • bash 쉘을 질문 태그로 가정합니다.

온라인 라이너는 아니지만 댓글이나 오류 메시지가 없으면 충분히 작습니다.

#!/bin/bash

LOCKFILE=/var/lock/TODO

set -o noclobber
exec {lockfd}<> "${LOCKFILE}" || exit 1
set +o noclobber # depends on what you need
flock --exclusive --nonblock ${lockfd} || exit 1

단, 댓글과 오류 메시지가 더 좋습니다.

#!/bin/bash

# TODO Set a lock file name
LOCKFILE=/var/lock/myprogram.lock

# Set noclobber option to ensure lock file is not REPLACED.
set -o noclobber

# Open lock file for R+W on a new file descriptor
# and assign the new file descriptor to "lockfd" variable.
# This does NOT obtain a lock but ensures the file exists and opens it.
exec {lockfd}<> "${LOCKFILE}" || {
  echo "pid=$$ failed to open LOCKFILE='${LOCKFILE}'" 1>&2
  exit 1
}

# TODO!!!! undo/set the desired noclobber value for the remainder of the script
set +o noclobber

# Lock on the allocated file descriptor or fail
# Adjust flock options e.g. --noblock as needed
flock --exclusive --nonblock ${lockfd} || {
  echo "pid=$$ failed to obtain lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'" 1>&2
  exit 1
}

# DO work here
echo "pid=$$ obtained exclusive lock fd='${lockfd}' LOCKFILE='${LOCKFILE}'"

# Can unlock after critical section and do more work after unlocking
#flock -u ${lockfd};
# if unlocking then might as well close lockfd too
#exec {lockfd}<&-

언급URL : https://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at

반응형