Szybki i brudny sposób zapewnienia, że ​​jednocześnie działa tylko jedna instancja skryptu powłoki


Odpowiedzi:


109

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).


73
Jak już wspomniano w komentarzu do innej odpowiedzi, ma to fatalną wadę - jeśli inny skrypt uruchamia się między czekiem a echem, toast.
Paul Tomblin,

1
Sztuczka z dowiązaniem symbolicznym jest fajna, ale jeśli właściciel pliku blokującego zabije -9'd lub system ulegnie awarii, nadal istnieje warunek wyścigu, aby odczytać dowiązanie symboliczne, zauważyć, że właściciela nie ma, a następnie usunąć. Trzymam się mojego rozwiązania.
bmdhacks

9
Kontrola atomowa i tworzenie jest dostępne w powłoce przy użyciu flocka (1) lub pliku blokującego (1). Zobacz inne odpowiedzi.
dmckee --- były moderator kociąt

3
Zobacz moją odpowiedź na przenośny sposób przeprowadzania kontroli atomowej i tworzenia bez konieczności polegania na narzędziach takich jak flock lub plik blokujący.
lhunath,

2
To nie jest atomowe i dlatego jest bezużyteczne. Potrzebujesz mechanizmu atomowego do testowania i ustawiania.
K Richard Pixley,

214

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.


11
Co to jest 200? W manulu jest napisane „fd”, ale nie wiem co to znaczy.
chovy

4
@chovy „deskryptor pliku”, uchwyt liczby całkowitej oznaczający otwarty plik.
Alex B

6
Jeśli ktoś zastanawia się: Składnia ( 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.
Dr Jan-Philip Gehrcke

1
Uważam, że kod wewnątrz podpowłoki powinien być bardziej podobny: 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?).
Jonathan Leffler,

1
Przekierowany plik jest tylko miejscem docelowym, w którym zamek może działać, nie ma w nim istotnych danych. exitJest od wewnętrznej części ( ). Po zakończeniu podprocesu blokada jest automatycznie zwalniana, ponieważ nie wstrzymuje go żaden proces.
clacke

158

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) .


1
Możesz połączyć tę 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.
Tobias Kienzler

4
Z pewnością jest prawdą, że 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.
lhunath,

5
Istnieje jednak sposób, aby sprawdzić, czy istnieje zwykły plik i utworzyć go atomowo, jeśli nie: za pomocą 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.
Juan Cespedes

4
Jest to „sposób sprawdzić, czy plik istnieje i utworzyć go w jednym działaniu atomowej” - to 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).
Toby Speight

5
„Wszystkie podejścia, które sprawdzają istnienie„ plików blokujących ”są wadliwe. Dlaczego? Ponieważ nie ma sposobu, aby sprawdzić, czy plik istnieje i utworzyć go w pojedynczej akcji atomowej.” - Aby uzyskać atomowość, należy to zrobić w poziom jądra - i odbywa się to na poziomie jądra przy pomocy flock (1) linux.die.net/man/1/flock, który pojawia się od daty praw autorskich człowieka co najmniej od 2006 roku. Więc napisałem recenzję (- 1), nic osobistego, po prostu mocno przekonajcie, że korzystanie z narzędzi jądra zaimplementowanych przez programistów jądra jest prawidłowe.
Craig Hicks

42

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.


3
flock()Wywołanie systemowe nie jest POSIX i nie działa dla plików na NFS.
maxschlepzig

17
Korzystając flock -x -n %lock file% -c "%command%"z zadania Crona, używam do upewnienia się, że tylko jedna instancja jest wykonywana.
Ryall,

Aww, zamiast niewyobrażalnego stada (1), powinni byli wybrać coś takiego jak stado (U). ... ma trochę znajomości. . . wydaje się, że słyszałem to wcześniej.
Kent Kruckeberg,

Warto zauważyć, że dokumentacja flock (2) określa użycie tylko z plikami, ale dokumentacja flock (1) określa użycie z plikiem lub katalogiem. Dokumentacja flock (1) nie mówi wprost o tym, jak wskazać różnicę podczas tworzenia, ale zakładam, że jest to zrobione przez dodanie końcowego „/”. W każdym razie, jeśli flock (1) może obsługiwać katalogi, ale flock (2) nie, wówczas flock (1) nie jest implementowany tylko w przypadku flock (2).
Craig Hicks

27

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.


1
Uruchom to kilka razy jednocześnie (np. „./A.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ”), a skrypt wycieknie kilka razy.
Nippysaurus,

7
@Nippysaurus: Ta metoda blokowania nie przecieka. To, co zobaczyłeś, to początkowy skrypt kończący się przed uruchomieniem wszystkich kopii, więc kolejny mógł (poprawnie) uzyskać blokadę. Aby uniknąć tego fałszywego pozytywu, dodaj sleep 10wcześniej rmdiri spróbuj ponownie kaskadowo - nic nie „wycieknie”.
Sir Athos,

Inne źródła twierdzą, że mkdir nie jest atomowy w niektórych systemach plików, takich jak NFS. A przy okazji widziałem sytuacje, w których na NFS współbieżne rekurencyjne mkdir czasami prowadzi do błędów przy zadaniach matrycy Jenkinsa. Więc jestem całkiem pewien, że tak jest. Ale mkdir jest całkiem niezły dla mniej wymagających przypadków użycia IMO.
akostadinov

Możesz użyć opcji noclobber Bash'a ze zwykłymi plikami.
Palec

26

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:

  • dowiązanie symboliczne
  • Przemianować

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.


Które strony NFS Illustrated obsługują stwierdzenie, że mkdir nie jest atomowy w stosunku do NFS?
maxschlepzig

Dziękujemy za tę technikę. Implementacja muteksu powłoki jest dostępna w mojej nowej bibliotece lib: github.com/Offirmo/offirmo-shell-lib , patrz „mutex”. Używa, lockfilejeśli jest dostępna, lub powrotu do tej symlinkmetody, jeśli nie.
Offirmo,

Miły. Niestety ta metoda nie zapewnia sposobu automatycznego usuwania starych blokad.
Richard Hansen

W przypadku dwustopniowego odblokowania ( 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.
Matt Wallis,

1
@MattWallis Ten ostatni problem można łatwo rozwiązać, dołączając go $$do ${f}.deletemenazwy pliku.
Stefan Majewsky

23

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

Bardzo nowatorskie podejście ... wydaje się, że jest to jeden ze sposobów na osiągnięcie atomowości za pomocą pliku blokady, a nie katalogu blokady.
Matt Caldwell

Niezłe podejście. :-) W pułapce WYJŚCIE powinien ograniczać, który proces może wyczyścić plik blokady. Na przykład: trap 'if [[$ (cat „$ lockfile”) == „$$”]]; następnie rm „$ lockfile”; fi 'EXIT
Kevin Seifert

1
Pliki blokady nie są atomowe w systemie plików NFS. dlatego ludzie zaczęli korzystać z blokad katalogów.
K Richard Pixley,

20

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.


Szkoda, że ​​ludzie niechętnie odrzucają bezużyteczne odpowiedzi: prowadzi to do tego, że nowe stosowne odpowiedzi są zakopywane w kupie śmieci.
Dmitry Grigoryev

4
Potrzebujemy tylko wielu pozytywnych opinii. To taka schludna i mało znana odpowiedź. (Choć pedantyczny OP chciał być szybki i brudny, podczas gdy jest to szybki i czysty!) Więcej na sempowiązane pytanie unix.stackexchange.com/a/322200/199525 .
Częściowe zachmurzenie

16

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.


2
Twój skrypt jest zbyt szczegółowy, myślę, że mógł być o wiele krótszy, ale ogólnie rzecz biorąc, tak, musisz ustawić pułapki, aby zrobić to poprawnie. Dodałbym również SIGHUP.
mojuba

Działa to dobrze, ale wydaje się, że sprawdza $ LOCK_DIR, podczas gdy usuwa $ __ lockdir. Może powinienem zasugerować przy usuwaniu blokady rm -r $ LOCK_DIR?
bevada

Dziękuję za sugestię. Powyższy kod został zniesiony i umieszczony w stylu kodu psuedo, więc będzie wymagał strojenia w zależności od użycia ludzi. Jednak celowo poszedłem z rmdir w moim przypadku, ponieważ rmdir bezpiecznie usuwa katalogi tylko jeśli są puste. Jeśli ludzie umieszczają w nich zasoby, takie jak pliki PID itp., Powinni zmienić czyszczenie blokady na bardziej agresywne 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.
Mark Stinson,

Testowałeś exit 1002?
Gilles Quenot

13

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. :)


Jak mogę to zasymulować, aby to przetestować? Czy istnieje sposób na uruchomienie skryptu dwa razy w jednym wierszu i może otrzymać ostrzeżenie, jeśli już działa?
rubo77

2
To w ogóle nie działa! Po co sprawdzać -gt 2? grep nie zawsze znajduje się w wyniku ps!
rubo77

pgrepnie ma w POSIX. Jeśli chcesz, aby działało to przenośnie, potrzebujesz POSIX psi przetwarzaj jego dane wyjściowe.
Palec

Na OSX -cnie istnieje, będziesz musiał użyć | wc -l. O porównaniu liczb: -gt 1jest sprawdzane, ponieważ pierwsza instancja widzi siebie.
Benjamin Peter,

6

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

5

Utworzyć plik blokady w znanej lokalizacji i sprawdzić, czy istnieje na początku skryptu? Umieszczenie PID w pliku może być pomocne, jeśli ktoś próbuje wyśledzić błędną instancję, która uniemożliwia wykonanie skryptu.


5

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


1
Możesz użyć ln -s, ponieważ może to tworzyć dowiązanie symboliczne tylko wtedy, gdy nie istnieje plik lub dowiązanie symboliczne, tak samo jak mkdir. w przeszłości wiele procesów systemowych używało dowiązań symbolicznych, na przykład init lub inetd. synlink zachowuje identyfikator procesu, ale tak naprawdę wskazuje na nic. przez lata to zachowanie się zmieniło. procesy wykorzystuje stada i semafory.
Znik

5

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.


4

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
}

Dzięki, sam wypróbowałem to na cygwin i przeszło proste testy.
ndemou

4

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 

3
Pomyślałem, że zwrócę uwagę, że -x (blokada zapisu) jest już ustawiona domyślnie.
Keldon Alleyne,

i -nzrobi to exit 1natychmiast, jeśli nie będzie w stanie uzyskać blokady
Anentropic

Dzięki @KeldonAlleyne zaktualizowałem kod, aby usunąć „-x”, ponieważ jest on domyślny.
presto8

3

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.


jak uzyskać lockfilenarzędzie?
Offirmo,

lockfilejest rozpowszechniany z procmail. Istnieje również alternatywa dotlockfiledla liblockfilepakietu. Obaj twierdzą, że działają niezawodnie na NFS.
Mr. Deathless,

3

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


3

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

1
Jest to wrażliwe na wyjście „ps” na moim komputerze (Ubuntu 14.04, / bin / ps z procps-ng wersja 3.3.9) polecenie „ps axf” drukuje znaki drzewa ascii, które zakłócają numery pól. To działało dla mnie: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
qneill

2

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

Dałem ci +1 za inne rozwiązanie. Mimo to nie działa ani w systemie AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: niepoprawna lista z opcją -o.) Nie HP-UX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: niedozwolona opcja - o). Dzięki.
Tagar

2

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.


1

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.


1

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.


1

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).


1

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

1

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.


1
Bez wywołania pułapki (lub przynajmniej czyszczenia w pobliżu końca w normalnym przypadku) masz fałszywie dodatni błąd, w którym plik blokujący pozostaje po ostatnim uruchomieniu, a PID został ponownie wykorzystany przez inny proces później. (A w najgorszym przypadku został obdarzony długim procesem, takim jak apache ....)
Philippe Chaintreuil

1
Zgadzam się, moje podejście jest wadliwe, potrzebuje pułapki. Zaktualizowałem swoje rozwiązanie. Nadal wolę nie mieć zewnętrznych zależności.
Filidor Wiese

1

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 

1

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.


0

Ś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.


0

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

7
Może to być problem, ale nie musi, w zależności od sposobu jego użycia, ale istnieje warunek wyścigu między testowaniem zamka i jego tworzeniem, aby oba skrypty mogły być uruchomione jednocześnie. Jeśli jedno zostanie zakończone jako pierwsze, drugie pozostanie uruchomione bez pliku blokady.
TimB

3
Wiadomości C, które nauczyły mnie wiele o przenośnych skryptach powłoki, zrobiły plik blokady. $$, a następnie próbowały połączyć go z „blokadą” - jeśli link się powiedzie, masz blokadę, w przeciwnym razie usuniesz blokadę. $$ i wyszedł.
Paul Tomblin,

To naprawdę dobry sposób, aby to zrobić, ale nadal musisz ręcznie usunąć plik blokujący, jeśli coś pójdzie nie tak i plik blokujący nie zostanie usunięty.
Matthew Scharley,

2
Szybki i brudny, o to prosił :)
Aupajo,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.