쉘 스크립트에서 올바른 잠금?

때로는 하나의 쉘 스크립트 인스턴스 만 동시에 실행되고 있는지 확인해야합니다.

예를 들어 자체 잠금 기능을 제공하지 않는 크론을 통해 실행되는 크론 작업 (예 : 기본 Solaris 크론).

잠금을 구현하는 일반적인 패턴은 다음과 같은 코드입니다.

#!/bin/sh
LOCK=/var/tmp/mylock
if [ -f $LOCK ]; then            # 'test' -> race begin
  echo Job is already running\!
  exit 6
fi
touch $LOCK                      # 'set'  -> race end
# do some work
rm $LOCK

물론 이러한 코드에는 경쟁 조건이 있습니다. 두 번째 인스턴스의 실행이 $LOCK파일 을 터치하기 전에 3 행 이후에 진행될 수있는 시간 창이 있습니다.

크론 작업의 경우 두 호출 사이에 분 간격이 있으므로 일반적으로 문제가되지 않습니다.

그러나 잠금 파일이 NFS 서버에있을 때와 같이 상황이 잘못 될 수 있습니다. 이 경우 여러 cron 작업이 3 행에서 차단되어 대기 할 수 있습니다. NFS 서버가 다시 활성화 된 경우 병렬로 실행중인 작업 무리 가 있습니다.

웹에서 검색 하면 해당 문제에 대한 좋은 해결책 인 것처럼 보이는 도구 lockrun 을 발견했습니다 . 그것으로 당신은 다음과 같이 잠금이 필요한 스크립트를 실행합니다 :

$ lockrun --lockfile=/var/tmp/mylock myscript.sh

이것을 랩퍼에 넣거나 crontab에서 사용할 수 있습니다.

사용 lockf()가능한 경우 (POSIX)를 사용하고 flock()(BSD)로 폴백합니다. 그리고 lockf()NFS를 통한 지원은 비교적 광범위해야합니다.

대안이 lockrun있습니까?

다른 크론 데몬은 어떻습니까? 제정신 방식으로 잠금을 지원하는 공통 크론이 있습니까? Vixie Crond (데비안 / 우분투 시스템의 기본값) 매뉴얼 페이지를 잠깐 살펴보면 잠금에 관한 내용이 표시되지 않습니다.

이 같은 도구를 포함하는 것이 좋습니다 있을까 lockrun로 coreutils를 ?

내 의견으로는 timeout, nice및 친구와 매우 유사한 주제를 구현합니다 .



답변

쉘 스크립트에서 잠금을 수행하는 또 다른 방법은 위에서 설명한 경쟁 조건을 방지 할 수 있으며 두 작업이 모두 행 3을 통과 할 수 있습니다.이 noclobber옵션은 ksh 및 bash에서 작동합니다. set noclobbercsh / tcsh에서 스크립팅해서는 안되므로 사용하지 마십시오 . 😉

lockfile=/var/tmp/mylock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null; then

        trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT

        # do stuff here

        # clean up after yourself, and release your trap
        rm -f "$lockfile"
        trap - INT TERM EXIT
else
        echo "Lock Exists: $lockfile owned by $(cat $lockfile)"
fi

NFS에 잠금 기능이있는 YMMV (NFS 서버에 연결할 수없는 경우)는 일반적으로 예전보다 훨씬 강력합니다. (10 년 전)

여러 서버에서 동시에 동일한 작업을 수행하는 크론 작업이 있지만 실제로 실행하기 위해 인스턴스가 하나만 있으면이 작업이 효과적 일 수 있습니다.

lockrun에 대한 경험은 없지만 스크립트를 실제로 실행하기 전에 미리 설정된 잠금 환경이 있으면 도움이 될 수 있습니다. 아니면 그렇지 않을 수도 있습니다. 당신은 래퍼에서 스크립트 외부의 잠금 파일에 대한 테스트를 설정하고 있으며 이론적으로는 두 개의 작업이 정확히 동일한 시간에 lockrun에 의해 호출 된 경우 ‘inside- 스크립트 솔루션?

파일 잠금은 어쨌든 시스템 동작을 거의 존중하며, 실행 전에 잠금 파일의 존재를 확인하지 않는 스크립트는 무엇이든 할 것입니다. 잠금 파일 테스트와 적절한 동작을 수행하면 100 %가 아니라도 99 %의 잠재적 문제를 해결할 수 있습니다.

잠금 파일 경쟁 조건이 많이 발생하는 경우 작업 시간이 제대로 지정되지 않았거나 작업 완료만큼 간격이 중요하지 않은 경우보다 큰 문제를 나타내는 것일 수 있습니다. .


아래 편집-2016-05-06 (KSH88을 사용하는 경우)


아래 @Clint Pachl의 의견을 바탕으로 ksh88을 사용 mkdir하는 경우 대신을 사용하십시오 noclobber. 이것은 대부분 잠재적 인 경쟁 조건을 완화하지만 완전히 제한하지는 않습니다 (위험은 미미합니다). 자세한 내용 은 아래 Clint가 게시 한 링크를 참조하십시오 .

lockdir=/var/tmp/mylock
pidfile=/var/tmp/mylock/pid

if ( mkdir ${lockdir} ) 2> /dev/null; then
        echo $$ > $pidfile
        trap 'rm -rf "$lockdir"; exit $?' INT TERM EXIT
        # do stuff here

        # clean up after yourself, and release your trap
        rm -rf "$lockdir"
        trap - INT TERM EXIT
else
        echo "Lock Exists: $lockdir owned by $(cat $pidfile)"
fi

또한 스크립트에서 tmpfile을 작성해야하는 경우 스크립트 종료시 lockdir정리 될 것임을 알고 디렉토리를 사용할 수 있습니다 .

보다 현대적인 bash의 경우 상단의 noclobber 방법이 적합해야합니다.


답변

하드 링크를 선호합니다.

lockfile=/var/lock/mylock
tmpfile=${lockfile}.$$
echo $$ > $tmpfile
if ln $tmpfile $lockfile 2>&-; then
    echo locked
else
    echo locked by $(<$lockfile)
    rm $tmpfile
    exit
fi
trap "rm ${tmpfile} ${lockfile}" 0 1 2 3 15
# do what you need to

하드 링크는 NFS를 통한 원 자성 이며 대부분의 경우 mkdir도 마찬가지 입니다. 실제적인 수준에서 사용 mkdir(2)하거나 link(2)거의 동일; NFS보다 많은 구현이 atomic보다 atomic hard link를 허용했기 때문에 하드 링크를 선호합니다 mkdir. 최신 NFS 릴리스를 사용하면 둘 중 어느 것을 사용해도 걱정할 필요가 없습니다.


답변

나는 그것이 mkdir원자 적이라는 것을 이해합니다 .

lockdir=/var/tmp/myapp
if mkdir $lockdir; then
  # this is a new instance, store the pid
  echo $$ > $lockdir/PID
else
  echo Job is already running, pid $(<$lockdir/PID) >&2
  exit 6
fi

# then set traps to cleanup upon script termination 
# ref http://www.shelldorado.com/goodcoding/tempfiles.html
trap 'rm -r "$lockdir" >/dev/null 2>&1' 0
trap "exit 2" 1 2 3 13 15


답변

쉬운 방법은 패키지 lockfile와 함께 제공되는 것 procmail입니다.

LOCKFILE="/tmp/mylockfile.lock"
# try once to get the lock else exit
lockfile -r 0 "$LOCKFILE" || exit 0

# here the actual job

rm -f "$LOCKFILE"


답변

semGNU parallel도구의 일부로 제공되는 것은 당신이 찾고있는 것일 수 있습니다.

sem [--fg] [--id <id>] [--semaphoretimeout <secs>] [-j <num>] [--wait] command

에서처럼 :

sem --id my_semaphore --fg "echo 1 ; date ; sleep 3" &
sem --id my_semaphore --fg "echo 2 ; date ; sleep 3" &
sem --id my_semaphore --fg "echo 3 ; date ; sleep 3" &

출력 :

1
Thu 10 Nov 00:26:21 UTC 2016
2
Thu 10 Nov 00:26:24 UTC 2016
3
Thu 10 Nov 00:26:28 UTC 2016

주문이 보장되지는 않습니다. 또한 출력은 끝날 때까지 표시되지 않습니다 (자극!). 그러나 그럼에도 불구하고 잠금 파일과 재시도 및 정리에 대해 걱정하지 않고 동시 실행을 방지하는 가장 간결한 방법입니다.


답변

사용 dtach합니다.

$ dtach -n /tmp/socket long_running_task ; echo $?
0
$ dtach -n /tmp/socket long_running_task ; echo $?
dtach: /tmp/socket: Address already in use
1


답변

여기여기에 설명 된대로 명령 행 도구 “flock”을 사용하여 bash 스크립트에서 잠금을 관리합니다 . 나는 무리 맨 페이지 에서이 간단한 방법을 사용하여 서브 쉘에서 일부 명령을 실행했습니다 …

   (
     flock -n 9
     # ... commands executed under lock ...
   ) 9>/var/lock/mylockfile

이 예에서는 잠금 파일을 얻을 수 없으면 종료 코드 1로 실패합니다. 그러나 무리는 명령을 하위 쉘에서 실행할 필요가없는 방식으로도 사용할 수 있습니다 🙂