Odpowiedzi:
Oto implementacja, która korzysta z pliku blokującego i wprowadza do niego echo PID. Służy to jako ochrona, jeśli proces zostanie zabity przed usunięciem pliku 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}
Sztuczka polega na kill -0tym, że nie dostarcza żadnego sygnału, a jedynie sprawdza, czy istnieje proces o danym PID. Również wywołanie trapzapewni, że plik blokujący zostanie usunięty, nawet jeśli proces zostanie zabity (z wyjątkiem kill -9).
Użyj, flock(1)aby zablokować wyłączny zakres deskryptora pliku. W ten sposób możesz nawet synchronizować różne części skryptu.
#!/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
Zapewnia to, że kod pomiędzy (i )jest uruchamiany tylko przez jeden proces na raz i że proces nie czeka zbyt długo na blokadę.
Zastrzeżenie: to konkretne polecenie jest częścią util-linux. Jeśli używasz systemu operacyjnego innego niż Linux, może on być dostępny lub może nie być dostępny.
( command A ) command Bwywołuje podpowłokę command A. Dokumentowane na tldp.org/LDP/abs/html/subshells.html . Nadal nie jestem pewien terminu wywołania podpowłoki i polecenia B.
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fitak, aby w przypadku przekroczenia limitu czasu (jakiś inny proces zablokował plik) skrypt ten nie działał i nie modyfikował pliku. Prawdopodobnie ... kontrargumentem jest „ale jeśli zajęło to 10 sekund, a blokada nadal nie jest dostępna, nigdy nie będzie dostępna”, prawdopodobnie dlatego, że proces trzymający blokadę nie kończy się (być może jest uruchamiany pod debuggerem?).
exitJest od wewnętrznej części ( ). Po zakończeniu podprocesu blokada jest automatycznie zwalniana, ponieważ nie wstrzymuje go żaden proces.
Wszystkie podejścia testujące istnienie „plików blokujących” są wadliwe.
Czemu? Ponieważ nie ma sposobu, aby sprawdzić, czy plik istnieje i utworzyć go w pojedynczej akcji atomowej. Z tego powodu; istnieje warunek wyścigu, który spowoduje, że twoje próby wzajemnego wykluczenia zostaną przerwane.
Zamiast tego musisz użyć mkdir. mkdirtworzy katalog, jeśli jeszcze nie istnieje, a jeśli tak, ustawia kod wyjścia. Co ważniejsze, robi to wszystko w jednej akcji atomowej, dzięki czemu idealnie nadaje się do tego scenariusza.
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
Aby uzyskać wszystkie szczegóły, zobacz doskonałe BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Jeśli chcesz zająć się przestarzałymi zamkami, przydaje się utrwalacz (1) . Jedynym minusem jest to, że operacja trwa około sekundy, więc nie jest natychmiastowa.
Oto funkcja, którą napisałem kiedyś, która rozwiązuje problem z użyciem utrwalacza:
# 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
}
Możesz użyć go w skrypcie takim jak:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Jeśli nie zależy ci na przenośności (te rozwiązania powinny działać prawie na każdym systemie UNIX), fuser Linuksa (1) oferuje kilka dodatkowych opcji, a także flock (1) .
if ! mkdirczęść ze sprawdzeniem, czy proces z PID zapisanym (przy pomyślnym uruchomieniu) wewnątrz lockdir faktycznie działa i jest identyczny ze skryptem do ochrony stalenes. Chroniłoby to również przed ponownym użyciem PID po ponownym uruchomieniu, a nawet nie wymagałoby fuser.
mkdirnie jest zdefiniowana jako operacja atomowa i jako taka „efekt uboczny” jest szczegółową implementacją systemu plików. W pełni mu wierzę, jeśli mówi, że NFS nie wdraża go w sposób atomowy. Chociaż nie podejrzewam, że /tmpbędziesz częścią NFS i zapewne dostarczy go fs, który implementuje mkdiratomowo.
lnutworzyć twarde łącze z innego pliku. Jeśli masz dziwne systemy plików, które tego nie gwarantują, możesz później sprawdzić i-węzeł nowego pliku, aby sprawdzić, czy jest on taki sam jak oryginalny plik.
open(... O_CREAT|O_EXCL). Potrzebujesz do tego odpowiedniego programu użytkownika, takiego jak lockfile-create(in lockfile-progs) lub dotlockfile(in liblockfile-bin). I upewnij się, że odpowiednio wyczyściłeś (np. trap EXIT) Lub przetestowałeś nieaktualne zamki (np. Z --use-pid).
Wokół wywołania systemowego flock (2) znajduje się opakowanie nazywane, niewyobrażalnie, flock (1). To sprawia, że względnie łatwo jest uzyskać wyłączne blokady bez obawy o oczyszczenie itp. Na stronie podręcznika znajdują się przykłady użycia go w skrypcie powłoki.
flock()Wywołanie systemowe nie jest POSIX i nie działa dla plików na NFS.
flock -x -n %lock file% -c "%command%"z zadania Crona, używam do upewnienia się, że tylko jedna instancja jest wykonywana.
Potrzebujesz operacji atomowej, takiej jak stado, inaczej to się ostatecznie nie powiedzie.
Ale co zrobić, jeśli stado nie jest dostępne. Cóż, jest mkdir. To także operacja atomowa. Tylko jeden proces zakończy się powodzeniem mkdir, wszystkie inne zakończą się niepowodzeniem.
Więc kod to:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
Musisz zająć się przestarzałymi blokadami po awarii, skrypt nigdy nie uruchomi się ponownie.
sleep 10wcześniej rmdiri spróbuj ponownie kaskadowo - nic nie „wycieknie”.
Aby blokowanie było niezawodne, potrzebujesz operacji atomowej. Wiele z powyższych propozycji nie ma charakteru atomowego. Proponowane narzędzie lockfile (1) wygląda obiecująco, jak wspomniała strona podręcznika, że jest „odporne na NFS”. Jeśli twój system operacyjny nie obsługuje pliku blokującego (1), a twoje rozwiązanie musi działać w systemie plików NFS, masz niewiele opcji ...
NFSv2 ma dwie operacje atomowe:
W NFSv3 wywołanie create jest również atomowe.
Operacje na katalogach NIE są atomowe pod NFSv2 i NFSv3 (patrz książka „NFS Illustrated” Brenta Callaghana, ISBN 0-201-32570-5; Brent jest weteranem NFS w Sun).
Wiedząc o tym, możesz zaimplementować blokady spinów dla plików i katalogów (w powłoce, a nie PHP):
zablokuj bieżący katalog:
while ! ln -s . lock; do :; done
zablokuj plik:
while ! ln -s ${f} ${f}.lock; do :; done
odblokuj bieżący katalog (założenie, że uruchomiony proces naprawdę uzyskał blokadę):
mv lock deleteme && rm deleteme
odblokuj plik (założenie, że uruchomiony proces naprawdę uzyskał blokadę):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Usuń również nie ma charakteru atomowego, dlatego najpierw należy zmienić nazwę (która jest atomowa), a następnie usunąć.
W przypadku wywołań dowiązania symbolicznego i zmiany nazwy oba nazwy plików muszą znajdować się w tym samym systemie plików. Moja propozycja: użyj tylko prostych nazw plików (bez ścieżek) i umieść plik i zablokuj w tym samym katalogu.
lockfilejeśli jest dostępna, lub powrotu do tej symlinkmetody, jeśli nie.
mv, rm) należy rm -fzastosować, a nie rmdwa wyścigi P1, P2? Na przykład P1 rozpoczyna odblokowywanie mv, następnie P2 blokuje, następnie P2 odblokowuje (oba mvi rm), w końcu P1 próbuje rmi kończy się niepowodzeniem.
$$do ${f}.deletemenazwy pliku.
Inną opcją jest użycie noclobberopcji powłoki przez uruchomienie set -C. Wtedy >się nie powiedzie, jeśli plik już istnieje.
W skrócie:
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
To powoduje, że powłoka wywołuje:
open(pathname, O_CREAT|O_EXCL)
który atomowo tworzy plik lub kończy się niepowodzeniem, jeśli plik już istnieje.
Zgodnie z komentarzem do BashFAQ 045 , to może się nie powieść ksh88, ale działa we wszystkich moich powłokach:
$ 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
Interesujące, że pdkshdodaje O_TRUNCflagę, ale oczywiście jest zbędne:
albo tworzysz pusty plik, albo nic nie robisz.
To, jak to zrobisz, rmzależy od tego, jak chcesz obsłużyć nieczyste wyjścia.
Usuń przy czystym wyjściu
Nowe uruchomienia kończą się niepowodzeniem, dopóki problem, który spowodował rozwiązanie nieczystego wyjścia, nie zostanie usunięty ręcznie.
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Usuń przy każdym wyjściu
Nowe uruchomienia zakończą się powodzeniem, pod warunkiem, że skrypt jeszcze nie działa.
trap 'rm "$lockfile"' EXIT
Możesz użyć GNU Paralleldo tego, ponieważ działa jako mutex, gdy jest wywoływany jako sem. Konkretnie możesz użyć:
sem --id SCRIPTSINGLETON yourScript
Jeśli chcesz również przekroczyć limit czasu, użyj:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Limit czasu <0 oznacza wyjście bez uruchamiania skryptu, jeśli semafor nie zostanie zwolniony w ramach limitu czasu, limit czasu> 0 oznacza uruchomienie skryptu i tak.
Pamiętaj, że powinieneś nadać mu nazwę (z --id), w przeciwnym razie domyślnie będzie to terminal kontrolny.
GNU Parallel to bardzo prosta instalacja na większości platform Linux / OSX / Unix - to tylko skrypt Perla.
sempowiązane pytanie unix.stackexchange.com/a/322200/199525 .
W przypadku skryptów powłoki zwykle używam mkdirwięcej, flockponieważ zamki są bardziej przenośne.
Tak czy inaczej, używanie set -enie wystarczy. To kończy działanie skryptu tylko w przypadku niepowodzenia dowolnego polecenia. Twoje zamki pozostaną w tyle.
Aby poprawnie wyczyścić blokadę, naprawdę powinieneś ustawić pułapki na coś takiego jak ten kod psuedo (podniesiony, uproszczony i nieprzetestowany, ale z aktywnie używanych skryptów):
#=======================================================================
# 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
Oto co się stanie. Wszystkie pułapki spowodują wyjście, więc funkcja __sig_exitbędzie zawsze działać (z wyjątkiem SIGKILL), który czyści twoje zamki.
Uwaga: moje wartości wyjściowe nie są wartościami niskimi. Czemu? Różne systemy przetwarzania wsadowego określają lub mają oczekiwania dotyczące liczb od 0 do 31. Ustawiając je na coś innego, mogę mieć moje skrypty i strumienie wsadowe odpowiednio reagujące na poprzednie zadanie wsadowe lub skrypt.
rm -r $LOCK_DIRlub nawet wymusić to w razie potrzeby (tak jak zrobiłem to również w szczególnych przypadkach, takich jak przechowywanie względnych plików zdrapek). Twoje zdrowie.
exit 1002?
Naprawdę szybki i naprawdę brudny? Ta linijka na górze skryptu będzie działać:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Oczywiście upewnij się, że nazwa skryptu jest unikalna. :)
-gt 2? grep nie zawsze znajduje się w wyniku ps!
pgrepnie ma w POSIX. Jeśli chcesz, aby działało to przenośnie, potrzebujesz POSIX psi przetwarzaj jego dane wyjściowe.
-cnie istnieje, będziesz musiał użyć | wc -l. O porównaniu liczb: -gt 1jest sprawdzane, ponieważ pierwsza instancja widzi siebie.
Oto podejście, które łączy atomowe blokowanie katalogu z sprawdzaniem, czy blokada jest przestarzała przez PID i restart, jeśli jest nieaktualna. Nie opiera się to również na żadnych baszizmach.
#!/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
Ten przykład jest wyjaśniony w man flock, ale wymaga pewnych ulepszeń, ponieważ powinniśmy zarządzać błędami i kodami wyjścia:
#!/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
Możesz użyć innej metody, wymień procesy, których użyłem w przeszłości. Jest to jednak bardziej skomplikowane niż powyższa metoda. Powinieneś wymienić procesy według ps, filtrować według jego nazwy, dodatkowy filtr grep -v grep dla usunięcia pasożyta i na końcu policzyć go przez grep -c. i porównaj z liczbą. To skomplikowane i niepewne
Istniejące opublikowane odpowiedzi albo polegają na narzędziu CLI, flockalbo nie zabezpieczają poprawnie pliku blokady. Narzędzie flock nie jest dostępne we wszystkich systemach innych niż Linux (tj. FreeBSD) i nie działa poprawnie w systemie plików NFS.
Na początku administrowania systemem i rozwoju systemu powiedziano mi, że bezpieczną i względnie przenośną metodą tworzenia pliku blokady jest utworzenie pliku tymczasowego przy użyciu mkemp(3)lub mkemp(1)zapisanie informacji identyfikujących w pliku tymczasowym (tj. PID), a następnie twardego łącza plik tymczasowy do pliku blokady. Jeśli połączenie się powiodło, oznacza to, że blokada została pomyślnie uzyskana.
Kiedy używam blokad w skryptach powłoki, zwykle umieszczam obtain_lock()funkcję we wspólnym profilu, a następnie pozyskuję ją ze skryptów. Poniżej znajduje się przykład mojej funkcji blokady:
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;
};
Oto przykład korzystania z funkcji blokady:
#!/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
Pamiętaj, aby zadzwonić clean_updo dowolnego punktu wyjścia w skrypcie.
Użyłem powyższego w środowiskach Linux i FreeBSD.
Podczas celowania w maszynę Debiana uważam, że lockfile-progspakiet jest dobrym rozwiązaniem. procmailjest również wyposażony w lockfilenarzędzie. Czasami jednak utknąłem z żadnym z nich.
Oto moje rozwiązanie, które wykorzystuje mkdiratomowość i plik PID do wykrywania starych blokad. Ten kod jest obecnie produkowany w konfiguracji Cygwin i działa dobrze.
Aby go użyć, po prostu zadzwoń, exclusive_lock_requiregdy potrzebujesz wyłącznego dostępu do czegoś. Opcjonalny parametr nazwy blokady pozwala udostępniać blokady między różnymi skryptami. Istnieją również dwie funkcje niższego poziomu ( exclusive_lock_tryi exclusive_lock_retry), jeśli potrzebujesz czegoś bardziej złożonego.
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
}
Jeśli ograniczenia stada, które zostały już opisane w innym miejscu tego wątku, nie stanowią dla ciebie problemu, to powinno działać:
#!/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
-nzrobi to exit 1natychmiast, jeśli nie będzie w stanie uzyskać blokady
Niektóre uniksy mają lockfilebardzo podobne do już wspomnianych flock.
Z strony podręcznika:
Pliku blokującego można użyć do utworzenia jednego lub więcej plików semaforów. Jeśli plik blokujący nie może utworzyć wszystkich określonych plików (w podanej kolejności), czeka na czas snu (domyślnie 8) sekund i ponawia próbę ostatniego pliku, który się nie powiódł. Możesz określić liczbę ponownych prób do momentu powrotu błędu. Jeśli liczba ponownych prób wynosi -1 (domyślnie, tj. -R-1) plik blokujący spróbuje ponownie na zawsze.
lockfilenarzędzie?
lockfilejest rozpowszechniany z procmail. Istnieje również alternatywa dotlockfiledla liblockfilepakietu. Obaj twierdzą, że działają niezawodnie na NFS.
W rzeczywistości, chociaż odpowiedź na bmdhacks jest prawie dobra, istnieje niewielka szansa, że drugi skrypt zostanie uruchomiony po pierwszym sprawdzeniu pliku blokującego i przed jego napisaniem. Obaj napiszą więc plik blokady i będą działać. Oto jak sprawić, by na pewno działało:
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
Ta noclobberopcja sprawi, że polecenie przekierowania zakończy się niepowodzeniem, jeśli plik już istnieje. Tak więc polecenie przekierowania jest w rzeczywistości atomowe - piszesz i sprawdzasz plik za pomocą jednego polecenia. Nie musisz usuwać pliku blokującego na końcu pliku - zostanie on usunięty przez pułapkę. Mam nadzieję, że pomoże to ludziom, którzy przeczytają to później.
PS Nie widziałem, że Mikel już poprawnie odpowiedział na pytanie, chociaż nie uwzględnił polecenia pułapki, aby zmniejszyć prawdopodobieństwo pozostawienia pliku blokady po zatrzymaniu skryptu na przykład za pomocą Ctrl-C. To jest kompletne rozwiązanie
Chciałem pozbyć się plików blokujących, lockdirów, specjalnych programów blokujących, a nawet pidofponieważ nie znaleziono go we wszystkich instalacjach Linuksa. Chciałem również mieć możliwie najprostszy kod (lub przynajmniej jak najmniej wierszy). Najprostsze ifzdanie, w jednym wierszu:
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; }'
Używam prostego podejścia, które obsługuje nieaktualne pliki blokad.
Zauważ, że niektóre z powyższych rozwiązań, które przechowują pid, ignorują fakt, że pid może się owijać. Tak więc - samo sprawdzenie, czy istnieje prawidłowy proces z przechowywanym pid, nie wystarczy, szczególnie w przypadku długo działających skryptów.
Używam noclobber, aby upewnić się, że tylko jeden skrypt może się otwierać i zapisywać do pliku blokady jednocześnie. Ponadto przechowuję wystarczającą ilość informacji, aby jednoznacznie zidentyfikować proces w pliku blokady. Definiuję zestaw danych, aby jednoznacznie zidentyfikować proces, który ma być pid, ppid, lstart.
Po uruchomieniu nowego skryptu, jeśli nie uda mu się utworzyć pliku blokady, następnie sprawdza, czy proces, który utworzył plik blokady, nadal istnieje. Jeśli nie, zakładamy, że pierwotny proces zmarł niemiłą śmiercią i pozostawił nieaktualny plik blokady. Nowy skrypt przejmuje własność pliku blokady i znów wszystko jest dobrze na świecie.
Powinien działać z wieloma powłokami na wielu platformach. Szybki, przenośny i prosty.
#!/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
Dodaj ten wiersz na początku skryptu
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
Jest to kod z bojlera od stada człowieka.
Jeśli chcesz więcej rejestrowania, użyj tego
[ "${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."
Ustawia i sprawdza zamki za pomocą flocknarzędzia. Ten kod wykrywa, czy został uruchomiony po raz pierwszy, sprawdzając zmienną FLOCKER, jeśli nie jest ustawiony na nazwę skryptu, a następnie próbuje ponownie uruchomić skrypt rekurencyjnie przy użyciu flocka i przy zainicjowanej zmiennej FLOCKER, jeśli FLOCKER jest ustawiony poprawnie, następnie flock na poprzedniej iteracji powiodło się i można kontynuować. Jeśli blokada jest zajęta, kończy się niepowodzeniem z konfigurowalnym kodem wyjścia.
Wygląda na to, że nie działa na Debianie 7, ale wydaje się, że działa ponownie z eksperymentalnym pakietem util-linux 2.25. Zapisuje „flock: ... Plik tekstowy zajęty”. Można to zmienić, wyłączając uprawnienia do zapisu w skrypcie.
PID i pliki blokujące są zdecydowanie najbardziej niezawodne. Podczas próby uruchomienia programu może on sprawdzić plik blokujący, który, a jeśli istnieje, może użyć, psaby sprawdzić, czy proces nadal działa. Jeśli tak nie jest, skrypt może się uruchomić, aktualizując PID w pliku blokady do własnego.
Uważam, że rozwiązanie bmdhack jest najbardziej praktyczne, przynajmniej w moim przypadku użycia. Używanie flocka i pliku blokującego polega na usunięciu pliku blokującego przy użyciu rm po zakończeniu skryptu, co nie zawsze jest gwarantowane (np. Zabić -9).
Chciałbym zmienić jedną drobną rzecz dotyczącą rozwiązania bmdhack: ma sens usunięcie pliku blokady, bez stwierdzenia, że nie jest to konieczne do bezpiecznego działania tego semafora. Jego użycie kill -0 zapewnia, że stary plik blokujący dla martwego procesu zostanie po prostu zignorowany / nadpisany.
Dlatego moim uproszczonym rozwiązaniem jest dodanie następujących elementów na górze singletonu:
## 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}
Oczywiście ten skrypt nadal ma tę wadę, że procesy, które prawdopodobnie rozpoczną się w tym samym czasie, stanowią zagrożenie wyścigu, ponieważ testy blokady i operacje ustawiania nie są pojedynczym działaniem atomowym. Ale proponowane przez lhunath rozwiązanie polegające na użyciu mkdir ma wadę polegającą na tym, że zabity skrypt może pozostawić katalog, uniemożliwiając w ten sposób uruchomienie innych instancji.
Narzędzie semaforyczne wykorzystuje flock(jak omówiono powyżej, np. Przez presto8) do implementacji semafora zliczającego . Umożliwia dowolną określoną liczbę współbieżnych procesów, jakie chcesz. Używamy go do ograniczania poziomu współbieżności różnych procesów roboczych kolejki.
To jest jak sem, ale o wiele lżejsze. (Pełne ujawnienie: napisałem to po stwierdzeniu, że sem był zbyt ciężki na nasze potrzeby i nie było dostępne proste narzędzie do liczenia semaforów).
Przykład z flock (1), ale bez podpowłoki. Plik flock () ed / tmp / foo nigdy nie jest usuwany, ale to nie ma znaczenia, ponieważ pobiera flock () i 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
Odpowiedział już milion razy, ale w inny sposób, bez potrzeby zewnętrznych zależności:
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
Za każdym razem zapisuje bieżący PID ($$) w pliku blokady, a przy uruchomieniu skryptu sprawdza, czy proces działa z najnowszym PID.
Korzystanie z blokady procesu jest znacznie silniejsze i dba również o nieżyczliwe wyjścia. plik_zablokowany jest otwarty tak długo, jak długo trwa proces. Zostanie zamknięty (przez powłokę), gdy proces będzie istniał (nawet jeśli zostanie zabity). Uważam, że jest to bardzo wydajne:
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
Używam oneliner @ od samego początku skryptu:
#!/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
Dobrze jest widzieć obecność procesu w pamięci (bez względu na status procesu); ale robi to za mnie.
Ścieżka trzody jest właściwą drogą. Zastanów się, co się stanie, gdy skrypt nagle umrze. W przypadku stada po prostu tracisz stado, ale to nie jest problem. Zauważ też, że zła sztuczka polega na tym, że stada samego skryptu ... ale to oczywiście pozwala na pełną parą naprzód problemy z uprawnieniami.
Szybko i brudno?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile