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 -0
tym, że nie dostarcza żadnego sygnału, a jedynie sprawdza, czy istnieje proces o danym PID. Również wywołanie trap
zapewni, ż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 B
wywoł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; fi
tak, 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?).
exit
Jest 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
. mkdir
tworzy 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 ! mkdir
część 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
.
mkdir
nie 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 /tmp
będziesz częścią NFS i zapewne dostarczy go fs, który implementuje mkdir
atomowo.
ln
utworzyć 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 10
wcześniej rmdir
i 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.
lockfile
jeśli jest dostępna, lub powrotu do tej symlink
metody, jeśli nie.
mv
, rm
) należy rm -f
zastosować, a nie rm
dwa wyścigi P1, P2? Na przykład P1 rozpoczyna odblokowywanie mv
, następnie P2 blokuje, następnie P2 odblokowuje (oba mv
i rm
), w końcu P1 próbuje rm
i kończy się niepowodzeniem.
$$
do ${f}.deleteme
nazwy pliku.
Inną opcją jest użycie noclobber
opcji 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 pdksh
dodaje O_TRUNC
flagę, ale oczywiście jest zbędne:
albo tworzysz pusty plik, albo nic nie robisz.
To, jak to zrobisz, rm
zależ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 Parallel
do 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.
sem
powiązane pytanie unix.stackexchange.com/a/322200/199525 .
W przypadku skryptów powłoki zwykle używam mkdir
więcej, flock
ponieważ zamki są bardziej przenośne.
Tak czy inaczej, używanie set -e
nie 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_exit
bę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_DIR
lub 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!
pgrep
nie ma w POSIX. Jeśli chcesz, aby działało to przenośnie, potrzebujesz POSIX ps
i przetwarzaj jego dane wyjściowe.
-c
nie istnieje, będziesz musiał użyć | wc -l
. O porównaniu liczb: -gt 1
jest 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, flock
albo 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_up
do 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-progs
pakiet jest dobrym rozwiązaniem. procmail
jest również wyposażony w lockfile
narzędzie. Czasami jednak utknąłem z żadnym z nich.
Oto moje rozwiązanie, które wykorzystuje mkdir
atomowość 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_require
gdy 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_try
i 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
-n
zrobi to exit 1
natychmiast, jeśli nie będzie w stanie uzyskać blokady
Niektóre uniksy mają lockfile
bardzo 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.
lockfile
narzędzie?
lockfile
jest rozpowszechniany z procmail
. Istnieje również alternatywa dotlockfile
dla liblockfile
pakietu. 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 noclobber
opcja 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 pidof
ponieważ nie znaleziono go we wszystkich instalacjach Linuksa. Chciałem również mieć możliwie najprostszy kod (lub przynajmniej jak najmniej wierszy). Najprostsze if
zdanie, 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ą flock
narzę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ć, ps
aby 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