bash 스크립트 인스턴스를 하나만 실행하는 방법은 무엇입니까?


26

추가 도구가 필요없는 솔루션이 선호됩니다.


잠금 파일은 어떻습니까?
Marco

@Marco 나는 발견 이 SO 응답 이를 사용하여,하지만 같이 주석에 언급 ,이 경쟁 조건 만들 수 있습니다
토비아스 Kienzler

3
이다 BashFAQ 45 .
jw013

@ jw013 감사합니다! 따라서 ln -s my.pid .lock잠금 ()이 오면 echo $$ > my.pid실패 할 때 저장된 PID .lock가 실제로 스크립트의 활성 인스턴스 인지 확인할 수 있습니다.
Tobias Kienzler

답변:


18

nsg의 답변과 거의 비슷합니다 : lock directory 사용하십시오 . 디렉토리 생성은 리눅스와 유닉스, * BSD와 다른 많은 OS에서 원자 적입니다.

if mkdir $LOCKDIR
then
    # Do important, exclusive stuff
    if rmdir $LOCKDIR
    then
        echo "Victory is mine"
    else
        echo "Could not remove lock dir" >&2
    fi
else
    # Handle error condition
    ...
fi

디버깅 목적으로 잠금 sh의 PID를 잠금 디렉토리의 파일에 넣을 수 있지만 잠금 프로세스가 여전히 실행 중인지 확인하기 위해 PID를 확인할 수 있다는 생각의 함정에 빠지지 마십시오. 많은 경쟁 조건이 그 길에 놓여 있습니다.


1
저장된 PID를 사용하여 잠금 인스턴스가 여전히 살아 있는지 확인하는 것이 좋습니다. 그러나 여기mkdir NFS에서 원 자성이 아니라는 주장이 있습니다 (나에게 해당되지는 않지만 사실이라면 언급해야 할 것 같습니다)
Tobias Kienzler

예, 반드시 저장된 PID를 사용하여 잠금 프로세스가 여전히 실행 중인지 확인하지만 메시지 로그 이외의 다른 작업을 시도하지 마십시오. 저장된 pid를 확인하고 새 PID 파일을 만드는 등의 작업은 경쟁에 큰 창을 남깁니다.
Bruce Ediger

좋아, Ihunath가 말했듯이 , lockdir /tmp은 대개 NFS 공유가 아닌 lockdir 일 가능성이 높 으므로 괜찮을 것입니다.
Tobias Kienzler

rm -rf잠금 디렉토리를 제거하는 데 사용 합니다. rmdir누군가 (필요하지는 않지만) 디렉토리에 파일을 추가하면 실패합니다.
chepner

18

에 추가하려면 브루스 Ediger의 대답 에 의해 영감 이 대답 , 당신은 또한 스크립트 종료에 대한 보호에 정리에 더 많은 영리를 추가해야합니다 :

#Remove the lock directory
function cleanup {
    if rmdir $LOCKDIR; then
        echo "Finished"
    else
        echo "Failed to remove lock directory '$LOCKDIR'"
        exit 1
    fi
}

if mkdir $LOCKDIR; then
    #Ensure that if we "grabbed a lock", we release it
    #Works for SIGTERM and SIGINT(Ctrl-C)
    trap "cleanup" EXIT

    echo "Acquired lock, running"

    # Processing starts here
else
    echo "Could not create lock directory '$LOCKDIR'"
    exit 1
fi

또는 if ! mkdir "$LOCKDIR"; then handle failure to lock and exit; fi trap and do processing after if-statement.
Kusalananda

6

너무 간단 할 수 있습니다. 틀린 경우 수정 해주세요. 간단 ps하지 않습니까?

#!/bin/bash 

me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;

# do stuff below this comment

1
좋고 그리고 / 또는 화려한. :)
Spooky

1
나는이 조건을 일주일 동안 사용했으며 두 번의 경우 새로운 프로세스가 시작되는 것을 막지 못했습니다. 나는 문제가 무엇인지 알아 냈다-새로운 pid는 오래된 것의 부분 문자열이며 숨겨진 다 grep -v $$. 실제 예 : old-14532, new-1453, old-28858, new-858.
Naktibalda

grep -v $$grep -v "^${$} "
Naktibalda

@Naktibalda 좋은 캐치, 감사합니다! 이 문제를 해결할 수도 있습니다 grep -wv "^$$"(편집 참조).
terdon

업데이트 해 주셔서 감사합니다. 짧은 pid가 공백으로 채워져있어 내 패턴이 때때로 실패했습니다.
Naktibalda

4

Marco가 언급했듯이 잠금 파일을 사용합니다.

#!/bin/bash

# Exit if /tmp/lock.file exists
[ -f /tmp/lock.file ] && exit

# Create lock file, sleep 1 sec and verify lock
echo $$ > /tmp/lock.file
sleep 1
[ "x$(cat /tmp/lock.file)" == "x"$$ ] || exit

# Do stuff
sleep 60

# Remove lock file
rm /tmp/lock.file

1
(잠금 파일을 만드는 것을 잊었다 고 생각합니다.) 경쟁 조건은 어떻습니까?
Tobias Kienzler

ops :) 예, 경쟁 조건은 내 예에서 문제입니다. 나는 보통 매시간 또는 매일 cron 작업을 작성하며 경쟁 조건은 거의 없습니다.
nsg

그들은 내 경우에도 관련이 없어야하지만 염두에 두어야 할 것입니다. 어쩌면 사용 lsof $0도 나쁘지 않습니까?
Tobias Kienzler

$$잠금 파일 에을 쓰면 경쟁 조건을 줄일 수 있습니다 . 그런 다음 sleep짧은 간격으로 다시 읽으십시오. PID가 여전히 귀하의 것이라면, 잠금을 성공적으로 획득했습니다. 추가 도구가 필요하지 않습니다.
manatwork

1
나는이 목적을 위해 lsof를 사용한 적이 없다. lsof는 시스템에서 실제로 느리고 (1-2 초) 경쟁 조건에 많은 시간이 걸릴 수 있습니다.
nsg

3

하나의 스크립트 인스턴스 만 실행되도록하려면 다음을 살펴보십시오.

병렬 실행에 대한 스크립트 잠금

그렇지 않으면 추가 도구를 호출하지 않기 때문에 check ps또는 invoke 할 수 lsof <full-path-of-your-script>있습니다.


보충 :

실제로 나는 다음과 같이 생각했습니다.

for LINE in `lsof -c <your_script> -F p`; do 
    if [ $$ -gt ${LINE#?} ] ; then
        echo "'$0' is already running" 1>&2
        exit 1;
    fi
done

이렇게하면 동시에 pid여러 인스턴스를 포크하고 실행하더라도 가장 낮은 프로세스 만 계속 실행됩니다 <your_script>.


1
링크에 감사하지만 답변에 필수 부분을 포함시킬 수 있습니까? 링크 부패를 방지하는 것이 SE의 일반적인 정책입니다 ... 그러나 [[(lsof $0 | wc -l) > 2]] && exit실제로는 이것으로 충분하거나 경쟁 조건에 취약합니까?
Tobias Kienzler

당신은 내 대답의 필수 부분이 누락되었고 링크를 게시하는 것은 꽤 절름발이 맞습니다. 나는 대답에 나 자신의 제안을 추가했다.
user1146332

3

bash 스크립트의 단일 인스턴스가 실행되도록하는 다른 방법 :

#!/bin/bash

# Check if another instance of script is running
pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1

...

pidof -o %PPID -x $0 기존 스크립트가 이미 실행중인 경우 PID를 가져 오거나 다른 스크립트가 실행되지 않으면 오류 코드 1로 종료합니다.


3

추가 도구가없는 솔루션을 요청했지만 다음을 사용하는 가장 좋아하는 방법입니다 flock.

#!/bin/sh

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

echo "servus!"
sleep 10

이는의 예제 섹션에서 제공되며 man flock추가 설명은 다음과 같습니다.

이것은 쉘 스크립트에 유용한 상용구 코드입니다. 잠 그려는 쉘 스크립트의 맨 위에 놓으면 첫 번째 실행에서 자동으로 잠 깁니다. env var $ FLOCKER가 실행중인 쉘 스크립트로 설정되지 않은 경우, 올바른 인수로 다시 실행하기 전에 flock을 실행하고 독점적 인 비 차단 잠금 (스크립트 자체를 잠금 파일로 사용)을 가져옵니다. 또한 FLOCKER env var를 올바른 값으로 설정하여 다시 실행되지 않습니다.

고려해야 할 사항 :

https://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at참조하십시오 .


1

이것은 Anselmo 's Answer의 수정 된 버전입니다 . 아이디어는 bash 스크립트 자체를 사용하여 읽기 전용 파일 설명자를 작성 flock하고 잠금을 처리하는 데 사용 하는 것입니다.

SCRIPT=`realpath $0`     # get absolute path to the script itself
exec 6< "$SCRIPT"        # open bash script using file descriptor 6
flock -n 6 || { echo "ERROR: script is already running" && exit 1; }   # lock file descriptor 6 OR show error message if script is already running

echo "Run your single instance code here"

다른 모든 답변과의 주요 차이점은이 코드는 파일 시스템을 수정하지 않고 매우 적은 공간을 사용하며 스크립트가 종료 상태와 독립적으로 완료되는 즉시 파일 설명자가 닫히므로 정리가 필요하지 않다는 것입니다. 따라서 스크립트가 실패하거나 성공하는지는 중요하지 않습니다.


타당한 이유가없는 한, 항상 모든 쉘 변수 참조를 인용해야하며 자신이하는 일을 확실하게 확신해야 합니다. 그래서 당신은해야합니다 exec 6< "$SCRIPT".
Scott

@Scott 귀하의 제안에 따라 코드를 변경했습니다. 많은 감사합니다.
John Doe

1

cksum을 사용하여 스크립트가 실제로 단일 인스턴스를 실행 하는지 확인하고 심지어 filename & file path를 변경합니다 .

트랩 및 잠금 파일을 사용하지 않습니다. 갑자기 서버가 다운되면 서버가 가동 된 후 수동으로 잠금 파일을 제거해야하기 때문입니다.

참고 : grep ps에는 첫 줄의 #! / bin / bash가 필요합니다.

#!/bin/bash

checkinstance(){
   nprog=0
   mysum=$(cksum $0|awk '{print $1}')
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(cksum /proc/$i/cwd/$cmd|awk '{print $1}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance 

#--- run your script bellow 

echo pass
while true;do sleep 1000;done

또는 스크립트 내에서 ckum을 하드 코딩 할 수 있으므로 스크립트의 파일 이름, 경로 또는 내용을 변경하려는 경우 다시 걱정할 필요가 없습니다 .

#!/bin/bash

mysum=1174212411

checkinstance(){
   nprog=0
   for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do 
        proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash) 
        if [[ $? -eq 0 ]];then 
           cmd=$(strings /proc/$i/cmdline|grep -v bash)
                if [[ $? -eq 0 ]];then 
                   fsum=$(grep mysum /proc/$i/cwd/$cmd|head -1|awk -F= '{print $2}')
                   if [[ $mysum -eq $fsum ]];then
                        nprog=$(($nprog+1))
                   fi
                fi
        fi
   done

   if [[ $nprog -gt 1 ]];then
        echo $0 is already running.
        exit
   fi
}

checkinstance

#--- run your script bellow

echo pass
while true;do sleep 1000;done

1
체크섬 하드 코딩이 얼마나 좋은지 정확하게 설명하십시오.
스콧

체크섬이 아니라 스크립트의 ID 키만 생성하며, 다른 인스턴스가 실행될 때 다른 쉘 스크립트 프로세스를 확인하고 ID 키가 해당 파일에있는 경우 먼저 파일을 분류하여 인스턴스가 이미 실행 중임을 의미합니다.
arputra

승인; 제발 편집 것을 설명하는 답변을. 그리고, 미래에 코드를하지 후 여러 30 라인 긴 블록을하시기 바랍니다 그들처럼하지 않고있는 거 (거의) 동일 설명 어떻게에게있는 거 다른합니다. 그리고하지 변수 이름을 계속 사용 "당신이 [원문] CKSUM 스크립트 내부에 하드 코딩 할 수 있습니다", 그리고 어떻게 같은 말을하지 않습니다 mysum그리고 fsum당신은 더 이상 체크섬에 대해 이야기하지 않을 때.
Scott

재미있어 보여요, 고마워요! 그리고 unix.stackexchange에 오신 것을 환영합니다 :)
Tobias Kienzler


0

당신에게 내 코드

#!/bin/bash

script_file="$(/bin/readlink -f $0)"
lock_file=${script_file////_}

function executing {
  echo "'${script_file}' already executing"
  exit 1
}

(
  flock -n 9 || executing

  sleep 10

) 9> /var/lock/${lock_file}

에 기초하여 man flock개선 만 :

  • 스크립트의 전체 이름을 기반으로하는 잠금 파일의 이름
  • 메시지 executing

여기 sleep 10에을 넣을 때 모든 기본 스크립트를 넣을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.