답변:
다음은 잠금 파일 을 사용 하고 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)
파일 기술자에 대한 독점적 인 범위의 잠금 A를 확인합니다. 이 방법으로 스크립트의 다른 부분을 동기화 할 수도 있습니다.
#!/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 이외의 운영 체제를 실행하는 경우 운영 체제가 사용 가능하지 않을 수 있습니다.
( command A ) command B
은에 대한 서브 쉘을 호출합니다 command A
. tldp.org/LDP/abs/html/subshells.html에 문서화되어 있습니다 . 나는 서브 쉘 및 명령 B의 호출의 타이밍에 대해 확실하지 아직 생각
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
생각합니다. 시간 초과가 발생하면 (일부 다른 프로세스에 파일이 잠겨있는 경우)이 스크립트는 진행되지 않고 파일을 수정하지 않습니다. 아마도 ... 반복 인수는 '10 초가 걸렸지 만 잠금을 여전히 사용할 수 없으면 절대 사용할 수 없게 될 것입니다. '아마도 잠금을 유지하는 프로세스가 종료되지 않기 때문일 수 있습니다. 디버거에서?).
exit
안쪽 부분에서이다 (
)
. 서브 프로세스가 종료되면 잠금을 보유하는 프로세스가 없으므로 잠금이 자동으로 해제됩니다.
"파일 잠금"의 존재를 테스트하는 모든 접근 방식에는 결함이 있습니다.
왜? 파일이 존재하는지 확인하고 단일 원자 조치로 파일을 작성할 수있는 방법이 없기 때문입니다. 이것 때문에; 경쟁 조건이 것입니다 상호 배제 휴식에 당신의 시도를이.
대신을 사용해야 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
오래된 잠금 장치를 관리하려면 퓨저 (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)도 있습니다.
if ! mkdir
lockdir 내부에 저장된 PID가있는 프로세스 (실패한 시작시)가 실제로 실행 중이고 스탈린 보호를위한 스크립트 와 동일한 지 여부를 확인 하는 데 파트를 결합 할 수 있습니다 . 또한 재부팅 후 PID를 재사용하지 못하도록 보호 할 수 있으며조차 필요하지 않습니다 fuser
.
mkdir
되지 않았 으며 "부작용"이 파일 시스템의 구현 세부 사항 인 것은 확실합니다 . 그가 NFS가 원자적인 방식으로 그것을 구현하지 않는다고 말하면 그를 완전히 믿습니다. 비록 당신 이 NFS 공유가 될 것이라고 생각하지는 않지만 원자 적으로 구현하는 fs에 의해 제공 될 것 입니다. /tmp
mkdir
ln
이 있습니다. 다른 파일에서 하드 링크를 만드는 데 사용 합니다. 보장 할 수없는 이상한 파일 시스템이있는 경우 나중에 새 파일의 inode를 검사하여 파일이 원본 파일과 동일한 지 확인할 수 있습니다.
open(... O_CREAT|O_EXCL)
. lockfile-create
(in lockfile-progs
) 또는 dotlockfile
(in liblockfile-bin
) 과 같이 적절한 사용자 프로그램이 필요합니다 . 또한 올바르게 청소 (예 trap EXIT
:)하거나 오래된 잠금 장치 (예 :)를 테스트하십시오 --use-pid
.
flock (2) 시스템 호출에는 flock (1)이라고하는 래퍼가 있습니다. 이렇게하면 정리 등을 걱정하지 않고 독점 잠금을 비교적 쉽게 얻을 수 있습니다 . 셸 스크립트에서이를 사용하는 방법 에 대한 예제 가 맨 페이지 에 있습니다.
flock()
시스템 호출은 POSIX하지 않고 NFS 마운트에서 파일을 작동하지 않습니다.
flock -x -n %lock file% -c "%command%"
하나의 인스턴스 만 실행되도록하기 위해 사용하는 Cron 작업에서 실행
무리와 같은 원자 연산이 필요합니다. 그렇지 않으면 결국 실패합니다.
그러나 무리를 사용할 수 없으면 어떻게해야합니까? mkdir이 있습니다. 그것은 원자 작업입니다. 하나의 프로세스 만 mkdir을 성공적으로 수행하고 다른 프로세스는 모두 실패합니다.
코드는 다음과 같습니다.
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
스크립트가 다시 실행되지 않는 충돌이 발생하면 오래된 잠금을 처리해야합니다.
sleep 10
이전을 추가하고 rmdir
다시 캐스케이드를 시도하십시오. "누설"이 없습니다.
잠금을 안정적으로 만들려면 원 자성 작업이 필요합니다. 위의 제안 중 많은 부분이 원자가 아닙니다. 제안 된 lockfile (1) 유틸리티는 언급 된 맨 페이지에서 "NFS- 내성"이라는 유망한 것으로 보입니다. OS가 lockfile (1)을 지원하지 않고 솔루션이 NFS에서 작동해야하는 경우 옵션이 많지 않습니다 ...
NFSv2에는 두 가지 원자 연산이 있습니다.
NFSv3을 사용하면 create 호출도 원 자성입니다.
디렉토리 작업은 NFSv2 및 NFSv3에서 원자 적이 지 않습니다 (Brent Callaghan의 ISBN '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
제거도 원자가 아니므로 먼저 원자 이름을 변경 한 다음 제거하십시오.
심볼릭 링크 및 이름 바꾸기 호출의 경우 두 파일 이름이 모두 동일한 파일 시스템에 있어야합니다. 내 제안 : 간단한 파일 이름 (경로 없음) 만 사용하고 파일을 넣고 동일한 디렉토리에 고정하십시오.
lockfile
가능한 경우, 또는 대체이로 symlink
하지 않을 경우 방법.
mv
, rm
)를 사용해야 합니까? 예를 들어, P1은로 잠금 해제를 시작한 다음 P2 잠금, P2 잠금 해제 ( 및 모두 )를 시작하고 마지막으로 P1이 시도 하고 실패합니다. rm -f
rm
mv
mv
rm
rm
$$
는 ${f}.deleteme
파일 이름 에 포함시켜 쉽게 완화 할 수 있습니다 .
또 다른 옵션은을 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 스크립트 일뿐입니다.
sem
관련 질문 unix.stackexchange.com/a/322200/199525 에서 자세히 알아보십시오 .
쉘 스크립트의 경우, 나는 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까지의 숫자를 기대하거나 갖습니다.이를 다른 것으로 설정하면 스크립트 및 배치 스트림이 이전 배치 작업 또는 스크립트에 따라 반응하도록 할 수 있습니다.
rm -r $LOCK_DIR
하거나 필요에 따라 강제로 수정해야합니다 (상대 스크래치 파일을 보유하는 것과 같은 특수한 경우에도 마찬가지입니다). 건배.
exit 1002
?
정말 빠르고 정말 더럽습니까? 스크립트 상단에있는이 하나의 라이너는 다음과 같이 작동합니다.
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
물론 스크립트 이름이 고유한지 확인하십시오. :)
-gt 2
합니까? grep이 ps의 결과에서 항상 자신을 찾지는 않습니다!
pgrep
POSIX에 없습니다. 이 작업을 이식 가능하게하려면 POSIX가 필요 ps
하고 출력을 처리하십시오.
-c
가 존재하지 않으면를 사용해야 | wc -l
합니다. 숫자 비교 정보 : -gt 1
첫 번째 인스턴스 자체를 확인한 후 확인됩니다.
원자 디렉토리 잠금과 PID를 통한 오래된 잠금 확인을 결합하고 오래된 경우 다시 시작하는 방법이 있습니다. 또한 이것은 bashism에 의존하지 않습니다.
#!/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
다른 방법을 사용하고 과거에 사용한 프로세스를 나열 할 수 있습니다. 그러나 이것은 위의 방법보다 더 복잡합니다. 기생충 nad 제거를 위해 ps, 프로세스 이름, 추가 필터 grep -v grep로 프로세스를 나열해야합니다. 숫자와 비교하십시오. 복잡하고 불확실하다
게시 된 기존 답변은 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
스크립트의 종료점 .
나는 리눅스와 FreeBSD 환경에서 위의 것을 사용했다.
데비안 머신을 대상으로 할 때 lockfile-progs
패키지가 좋은 해결책이라고 생각합니다. 도구 procmail
도 함께 제공됩니다 lockfile
. 그러나 때때로 나는 이것들 중 어느 것에도 붙어 있지 않습니다.
다음 mkdir
은 원 자성 및 PID 파일을 사용하여 오래된 잠금을 감지하는 솔루션입니다 . 이 코드는 현재 Cygwin 설정에서 제작 중이며 제대로 작동합니다.
그것을 사용하려면 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
-n
것입니다 exit 1
그것은 잠금을 얻을 수없는 경우 즉시
일부 유닉스는 lockfile
이미 언급 한 것과 매우 유사합니다 flock
.
맨 페이지에서 :
lockfile은 하나 이상의 세마포어 파일을 작성하는 데 사용될 수 있습니다. lock-file이 지정된 모든 파일을 지정된 순서대로 작성할 수 없으면 휴면 시간 (기본값 8 초) 동안 대기 한 후 성공하지 못한 마지막 파일을 재 시도합니다. 실패가 리턴 될 때까지 재시도 횟수를 지정할 수 있습니다. 재시도 횟수가 -1 (기본값, -r-1)이면 잠금 파일이 영구적으로 재 시도됩니다.
lockfile
유틸리티를 얻 습니까?
lockfile
와 함께 배포됩니다 procmail
. 또한 패키지 dotlockfile
와 함께 제공 되는 대안 이 liblockfile
있습니다. 둘 다 NFS에서 안정적으로 작동한다고 주장합니다.
실제로 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로 스크립트를 중지 한 후 잠금 파일이 남을 가능성을 줄이기 위해 trap 명령을 포함하지 않았습니다. 이것이 완벽한 솔루션입니다
잠금 파일, lockdir, 특수 잠금 프로그램을 제거하고 pidof
모든 Linux 설치에서 찾을 수 없기 때문에 제거하고 싶었습니다 . 또한 가능한 가장 간단한 코드 (또는 가능한 한 적은 수의 행)를 원했습니다. if
한 줄로 가장 간단한 진술 :
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $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
스크립트 시작 부분에이 줄을 추가하십시오
[ "${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 변수가 초기화 된 상태에서 스크립트를 다시 재귀 적으로 시작하려고 시도합니다. 성공했고 계속해도 괜찮습니다. 잠금이 사용 중이면 구성 가능한 종료 코드로 실패합니다.
데비안 7에서는 작동하지 않는 것 같지만 실험적인 util-linux 2.25 패키지에서는 다시 작동하는 것 같습니다. "flock : ... Text file busy"라고 씁니다. 스크립트에 대한 쓰기 권한을 비활성화하여 재정의 할 수 있습니다.
bmdhack의 솔루션이 적어도 내 유스 케이스에서 가장 실용적인 것으로 나타났습니다. flock 및 lockfile을 사용하면 스크립트가 종료 될 때 rm을 사용하여 lockfile을 제거해야하므로 항상 보장 할 수는 없습니다 (예 : 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 의해 예 상술)을 구현하는 카운팅 세마포어 . 원하는 특정 수의 동시 프로세스를 사용할 수 있습니다. 다양한 큐 워커 프로세스의 동시성 레벨을 제한하기 위해이를 사용합니다.
그것은 sem과 같지만 훨씬 가볍습니다. (전체 공개 : 나는 sem이 우리의 요구에 너무 무거워서 사용할 수있는 간단한 세마포어 유틸리티가 없다는 것을 알게 된 후에 썼다.)
flock (1)이지만 서브 쉘이없는 예. 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
외부 의존성이 필요없는 이미 백만 번이나 다른 방법으로 답변했습니다.
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
빠르고 더러운?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile