Prawidłowe blokowanie w skryptach powłoki?


66

Czasami musisz się upewnić, że jednocześnie działa tylko jedna instancja skryptu powłoki.

Na przykład zadanie cron, które jest wykonywane przez crond, które nie zapewnia samodzielnego blokowania (np. Domyślny crond Solaris).

Typowym wzorem do implementacji blokowania jest następujący kod:

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

Oczywiście taki kod ma warunek wyścigu. Istnieje okno czasowe, w którym wykonanie dwóch wystąpień może przejść do następnego wiersza 3, zanim będzie można dotknąć $LOCKpliku.

W przypadku zadania cron zwykle nie stanowi to problemu, ponieważ odstęp między kolejnymi wywołaniami wynosi kilka minut.

Ale coś może pójść nie tak - na przykład, gdy plik blokujący znajduje się na serwerze NFS - który się zawiesza. W takim przypadku kilka zadań cron może zablokować na linii 3 i ustawić w kolejce. Jeśli serwer NFS jest ponownie aktywny, oznacza to, że istnieje stado grzmotu równolegle uruchomionych zadań.

Podczas wyszukiwania w Internecie znalazłem narzędzie lockrun, które wydaje się dobrym rozwiązaniem tego problemu. Za jego pomocą uruchamiasz skrypt, który wymaga zablokowania w następujący sposób:

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

Możesz to umieścić w opakowaniu lub użyć ze swojego crontab.

Wykorzystuje lockf()(POSIX), jeśli jest dostępny, i wraca do flock()(BSD). I lockf()wsparcie przez NFS powinien być stosunkowo powszechne.

Czy są alternatywy dla lockrun?

Co z innymi demonami cron? Czy istnieją popularne crondy, które obsługują blokowanie w rozsądny sposób? Szybkie spojrzenie na stronę manuala Vixie Crond (domyślnie w systemach Debian / Ubuntu) nie pokazuje nic na temat blokowania.

Czy dobrym pomysłem byłoby włączenie takiego narzędzia lockrundo coreutils ?

Moim zdaniem realizuje motyw bardzo podobny do timeout, nicei przyjaciół.


4
Stycznie i na korzyść innych, którzy mogą uznać twój początkowy wzorzec za wystarczający (tm), ten kod powłoki powinien prawdopodobnie uwięzić TERM w celu usunięcia jego pliku blokującego podczas killedycji; i wydaje się, że dobrą praktyką jest przechowywanie własnego pid w pliku blokującym, niż tylko dotykanie go.
Ulrich Schwarz,


@Shawn, tak naprawdę, nie wspomina o crond i NFS.
maxschlepzig,

powiązane pytanie w sprawie SO: stackoverflow.com/questions/185451/…
maxschlepzig

1
@Ulrich bardzo spóźniony, przechowywanie PID w pliku blokującym NFS ma bardzo niewielką wartość. Nawet dodanie hosta nadal nie naprawdę pomóc w procesie sprawdzania żywo
roaima

Odpowiedzi:


45

Oto inny sposób na zablokowanie w skrypcie powłoki, który może zapobiec opisanemu powyżej wyścigowi, w którym dwa zadania mogą przekroczyć linię 3. noclobberOpcja będzie działać w ksh i bash. Nie używaj, set noclobberponieważ nie powinieneś pisać skryptów w csh / tcsh. ;)

lockfile=/var/tmp/mylock

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

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

        # do stuff here

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

YMMV z blokowaniem na NFS (wiesz, kiedy serwery NFS są nieosiągalne), ale ogólnie jest o wiele bardziej solidny niż kiedyś. (10 lat temu)

Jeśli masz zadania cron, które robią to samo w tym samym czasie, z wielu serwerów, ale potrzebujesz tylko 1 instancji, aby faktycznie uruchomić, coś takiego może działać dla Ciebie.

Nie mam doświadczenia z lockrun, ale posiadanie wstępnie ustawionego środowiska blokady przed uruchomieniem skryptu może pomóc. A może nie. Po prostu ustawiasz test pliku blokady poza skryptem w opakowaniu i teoretycznie, czy nie mógłbyś po prostu osiągnąć tego samego stanu wyścigu, gdyby lockrun wykonał dwa zadania dokładnie w tym samym czasie, tak jak w przypadku „inside- rozwiązanie skryptu?

Blokowanie plików i tak jest w zasadzie zachowaniem systemu honorowego, a wszelkie skrypty, które nie sprawdzają istnienia pliku blokującego przed uruchomieniem, zrobią wszystko, co zamierzają. Wystarczy, że przejdziesz test pliku blokady i odpowiednie zachowanie, rozwiążesz 99% potencjalnych problemów, jeśli nie 100%.

Jeśli często spotykasz się z warunkami wyścigu plików blokujących, może to wskazywać na większy problem, na przykład brak prawidłowego zaplanowania zadań, a może jeśli interwał nie jest tak ważny jak ukończenie zadania, być może Twoje zadanie lepiej nadaje się do demonizacji .


EDYTUJ PONIŻEJ - 2016-05-06 (jeśli używasz KSH88)


Oprzyj się na komentarzu @Clint Pachl poniżej, jeśli używasz ksh88, użyj mkdirzamiast noclobber. W większości przypadków łagodzi to potencjalny stan wyścigu, ale nie ogranicza go całkowicie (choć ryzyko jest niewielkie). Aby uzyskać więcej informacji, przeczytaj link zamieszczony poniżej przez Clinta .

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

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

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

Dodatkową zaletą jest to, że jeśli musisz utworzyć pliki tmp w swoim skrypcie, możesz użyć lockdirdla nich katalogu, wiedząc, że zostaną one wyczyszczone po zakończeniu skryptu.

Aby uzyskać bardziej nowoczesny bash, odpowiednia powinna być metoda noclobber na górze.


1
Nie, z lockrun nie masz problemu - gdy serwer NFS zawiesi się, wszystkie wywołania lockrun zawiesią się (przynajmniej) w lockf()wywołaniu systemowym - gdy zostanie ono wykonane, wszystkie procesy zostaną wznowione, ale tylko jeden proces wygra blokadę. Brak warunków wyścigu. Nie mam takich problemów z cronjobs - wręcz przeciwnie - ale jest to problem, gdy cię uderza, może powodować wiele bólu.
maxschlepzig

1
Przyjąłem tę odpowiedź, ponieważ metoda jest bezpieczna i jak dotąd najbardziej elegancka. Proponuję mały wariant: set -o noclobber && echo "$$" > "$lockfile"aby uzyskać bezpieczny powrót, gdy powłoka nie obsługuje opcji noclobber.
maxschlepzig,

3
Dobra odpowiedź, ale powinieneś także „zabić -0” wartość w pliku blokującym, aby mieć pewność, że proces, który utworzył blokadę, nadal istnieje.
Nigel Horne

1
noclobberOpcja może być podatna na wyścig warunki. Zajrzyj na mywiki.wooledge.org/BashFAQ/045, aby znaleźć coś do przemyślenia.
Clint Pachl,

2
Uwaga: użycie noclobber(lub -C) w ksh88 nie działa, ponieważ ksh88 nie używa O_EXCLdla noclobber. Jeśli korzystasz z nowszej powłoki, możesz być w porządku ...
jrw32982,

14

Wolę używać twardych linków.

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

Twarde linki są atomowe w stosunku do NFS i w przeważającej części również mkdir . Używanie mkdir(2)lub link(2)jest prawie takie samo, na poziomie praktycznym; Po prostu wolę używać twardych dowiązań, ponieważ więcej implementacji NFS dopuszcza atomowe dowiązania twarde niż atomowe mkdir. Dzięki nowoczesnym wersjom NFS nie powinieneś się martwić korzystaniem z jednego z nich.


12

Rozumiem, że mkdirto atom, więc być może:

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

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

Ok, ale nie mogłem znaleźć informacji, czy mkdir()ponad NFS (> = 3) jest standaryzowany jako atomowy.
maxschlepzig

2
@maxschlepzig RFC 1813 nie wzywa wprost mkdirdo bycia atomowym (tak jest rename). W praktyce wiadomo, że niektóre implementacje nie są. Powiązane: ciekawy wątek, w tym wkład autora GNU arch .
Gilles

8

Prostym sposobem jest użycie dostarczanego lockfilezwykle wraz z procmailpakietem.

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

# here the actual job

rm -f "$LOCKFILE"

5

semktóry jest częścią parallelnarzędzi GNU , może być tym, czego szukasz:

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

Jak w:

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

wyjście:

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

Pamiętaj, że zamówienie nie jest gwarantowane. Również wyjście nie jest wyświetlane, dopóki się nie skończy (irytujące!). Ale mimo to jest to najbardziej zwięzły sposób, jaki znam, aby uchronić się przed równoczesnym wykonywaniem, nie martwiąc się o pliki blokujące, próby i czyszczenie.


Czy blokowanie oferowane przez semklamkę zostało zestrzelone w trakcie wykonywania?
roaima,

2

Używam dtach.

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

1

Korzystam z narzędzia wiersza polecenia „flock” do zarządzania blokadami w skryptach bash, jak opisano tutaj i tutaj . Użyłem tej prostej metody ze strony stada, aby uruchomić niektóre polecenia w podpowłoce ...

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

W tym przykładzie nie działa z kodem wyjścia 1, jeśli nie może uzyskać pliku blokującego. Ale flocka można również używać w sposób, który nie wymaga uruchamiania poleceń w podpowłoce :-)


3
flock()Wywołanie systemowe nie działa przez NFS .
maxschlepzig

1
BSD ma podobne narzędzie „lockf”.
dubiousjim

2
@diousious, BSD lockf również wywołuje flock()i dlatego jest problematyczny w stosunku do NFS. W międzyczasie flock () w Linuksie teraz powraca do fcntl()momentu, gdy plik znajduje się na wierzchowcu NFS, a zatem w środowisku NFS opartym tylko na Linuksie flock()działa teraz na NFS.
maxschlepzig

1

Nie używaj pliku.

Jeśli skrypt jest wykonywany w ten sposób, np .:

bash my_script

Możesz sprawdzić, czy działa, używając:

running_proc=$(ps -C bash -o pid=,cmd= | grep my_script);
if [[ "$running_proc" != "$$ bash my_script" ]]; do 
  echo Already locked
  exit 6
fi

Hm, kod sprawdzający ps działa od wewnątrz my_script? W przypadku, gdy działa inne wystąpienie - nie running_proczawiera dwóch pasujących wierszy? Podoba mi się pomysł, ale oczywiście - dostaniesz fałszywe wyniki, gdy inny użytkownik jest uruchomiony skrypt o tej samej nazwie ...
maxschlepzig

3
Obejmuje również warunek wyścigu: jeśli 2 instancje wykonają pierwszą linię równolegle, to żadna nie dostanie „blokady” i obie wyjdą ze statusem 6. Byłby to rodzaj jednej rundy wzajemnego głodu . Przy okazji, nie jestem pewien, dlaczego używasz $!zamiast $$w swoim przykładzie.
maxschlepzig

@maxschlepzig rzeczywiście przepraszam za nieprawidłowe $! vs. $$
frogstarr78

@maxschlepzig, aby obsłużyć wielu użytkowników uruchamiających skrypt, dodaj euser = do argumentu -o.
frogstarr78

@maxschlepzig, aby zapobiec wielu wierszom, możesz również zmienić argumenty na grep lub dodatkowe „filtry” (np grep -v $$.). Zasadniczo próbowałem przedstawić inne podejście do problemu.
frogstarr78

1

Do faktycznego użycia należy użyć odpowiedzi, która została najwyżej oceniona .

Chciałbym jednak omówić różne zepsute i na wpół wykonalne podejścia psoraz wiele ostrzeżeń, jakie mają, ponieważ wciąż widzę, jak ludzie z nich korzystają.

Ta odpowiedź to tak naprawdę odpowiedź na pytanie „Dlaczego nie użyć psi grepporadzić sobie z blokowaniem w powłoce?”

Zepsute podejście # 1

Po pierwsze, podejście podane w innej odpowiedzi, które ma kilka pozytywnych opinii, mimo że nie działa (i nigdy nie mogło) działać i najwyraźniej nigdy nie zostało przetestowane:

running_proc=$(ps -C bash -o pid=,cmd= | grep my_script);
if [[ "$running_proc" != "$$ bash my_script" ]]; do 
  echo Already locked
  exit 6
fi

Naprawmy błędy składniowe i zepsute psargumenty i uzyskajmy:

running_proc=$(ps -C bash -o pid,cmd | grep "$0");
echo "$running_proc"
if [[ "$running_proc" != "$$ bash $0" ]]; then
  echo Already locked
  exit 6
fi

Ten skrypt zawsze kończy działanie 6, za każdym razem, bez względu na to, jak go uruchomisz.

Jeśli uruchomisz go ./myscript, pswynik będzie po prostu taki 12345 -bash, który nie będzie pasował do wymaganego ciągu 12345 bash ./myscript, więc to się nie powiedzie.

Jeśli go uruchomisz bash myscript, wszystko stanie się bardziej interesujące. Proces bash widły do uruchomienia rurociągu, a dziecko powłoka uruchamia psi grep. Zarówno powłoka oryginalna, jak i podrzędna zostaną wyświetlone w danych pswyjściowych, mniej więcej tak:

25793 bash myscript
25795 bash myscript

Nie jest to oczekiwany wynik $$ bash $0, więc skrypt zostanie zamknięty.

Zepsute podejście # 2

Teraz, uczciwie wobec użytkownika, który napisał złamane podejście nr 1, zrobiłem coś podobnego, kiedy po raz pierwszy spróbowałem tego:

if otherpids="$(pgrep -f "$0" | grep -vFx "$$")" ; then
  echo >&2 "There are other copies of the script running; exiting."
  ps >&2 -fq "${otherpids//$'\n'/ }" # -q takes about a tenth the time as -p
  exit 1
fi

To prawie działa. Ale fakt rozwidlenia się, aby poprowadzić rurę, odrzuca to. Więc ten również zawsze wyjdzie.

Nierzetelne podejście # 3

pids_this_script="$(pgrep -f "$0")"
if not_this_process="$(echo "$pids_this_script" | grep -vFx "$$")"; then
  echo >&2 "There are other copies of this script running; exiting."
  ps -fq "${not_this_process//$'\n'/ }"
  exit 1
fi

Ta wersja pozwala uniknąć problemu rozwidlenia potoku w podejściu nr 2, najpierw pobierając wszystkie PID, które mają bieżący skrypt w argumentach wiersza poleceń, a następnie osobno filtrując tę ​​listę pid, aby pominąć PID bieżącego skryptu.

Może to działać ... pod warunkiem, że żaden inny proces nie ma wiersza polecenia pasującego do $0, a jeśli skrypt jest zawsze wywoływany w ten sam sposób (np. Jeśli jest wywoływany ze ścieżką względną, a następnie ścieżką bezwzględną, druga instancja nie zauważy pierwszej ).

Nierzetelne podejście # 4

A co jeśli pominiemy sprawdzanie pełnego wiersza poleceń, ponieważ może to nie oznaczać, że skrypt faktycznie działa, i lsofzamiast tego sprawdzimy, aby znaleźć wszystkie procesy, które mają ten skrypt otwarty?

Cóż, tak, to podejście nie jest wcale takie złe:

if otherpids="$(lsof -t "$0" | grep -vFx "$$")"; then
  echo >&2 "Error: There are other processes that have this script open - most likely other copies of the script running.  Exiting to avoid conflicts."
  ps >&2 -fq "${otherpids//$'\n'/ }"
  exit 1
fi

Oczywiście, jeśli kopia skryptu jest uruchomiona, nowa instancja uruchomi się dobrze i będziesz mieć uruchomione dwie kopie .

Lub jeśli uruchomiony skrypt zostanie zmodyfikowany (np. Za pomocą Vima lub a git checkout), wówczas „nowa” wersja skryptu uruchomi się bez problemu, ponieważ zarówno Vim, jak i git checkoutnowy plik (nowy i-węzeł) zamiast stary.

Jednakże, jeśli skrypt nie jest modyfikowana i nigdy kopiowane, to ta wersja jest bardzo dobra. Nie ma warunków wyścigu, ponieważ plik skryptu musi już być otwarty, aby można było sprawdzić czek.

Nadal mogą występować fałszywe alarmy, jeśli inny proces ma otwarty plik skryptu, ale pamiętaj, że nawet jeśli jest on otwarty do edycji w Vimie, vim tak naprawdę nie utrzymuje otwartego pliku skryptu, więc nie spowoduje to fałszywych alarmów.

Ale pamiętaj, nie używaj tego podejścia, jeśli skrypt może być edytowany lub kopiowany, ponieważ otrzymasz fałszywe negatywy, tj. Wiele instancji działających jednocześnie - więc fakt, że edycja za pomocą Vima nie daje fałszywych wyników pozytywnych, nie powinien mieć znaczenia do Ciebie. Wspominam go jednak, ponieważ podejście nr 3 nie daje wyników fałszywie dodatnich (tj odmawia start) jeśli masz skrypt otwarty z Vima.

Co więc zrobić?

Najwyższej głosowało odpowiedź na to pytanie daje solidne podejście.

Być może możesz napisać lepszy ... ale jeśli nie rozumiesz wszystkich problemów i zastrzeżeń we wszystkich powyższych podejściach, prawdopodobnie nie napiszesz metody blokowania, która omija je wszystkie.



0

Oto coś, co czasami dodaję na serwerze, aby łatwo obsługiwać warunki wyścigu dla każdego zadania na maszynie. To jest podobne do postu Tima Kennedy'ego, ale w ten sposób dostajesz obsługę wyścigu, dodając tylko jeden wiersz do każdego skryptu bash, który tego potrzebuje.

Umieść poniższe treści w np. / Opt / racechecker / racechecker:

ZPROGRAMNAME=$(readlink -f $0)
EZPROGRAMNAME=`echo $ZPROGRAMNAME | sed 's/\//_/g'`
EZMAIL="/usr/bin/mail"
EZCAT="/bin/cat"

if  [ -n "$EZPROGRAMNAME" ] ;then
        EZPIDFILE=/tmp/$EZPROGRAMNAME.pid
        if [ -e "$EZPIDFILE" ] ;then
                EZPID=$($EZCAT $EZPIDFILE)
                echo "" | $EZMAIL -s "$ZPROGRAMNAME already running with pid $EZPID"  alarms@someemail.com >>/dev/null
                exit -1
        fi
        echo $$ >> $EZPIDFILE
        function finish {
          rm  $EZPIDFILE
        }
        trap finish EXIT
fi

Oto jak z niego korzystać. Zwróć uwagę na wiersz po shebang:

     #/bin/bash
     . /opt/racechecker/racechecker
     echo "script are running"
     sleep 120

Działa w ten sposób, że rozpoznaje nazwę głównego pliku bashscript i tworzy plik pid pod „/ tmp”. Dodaje także detektor do sygnału zakończenia. Słuchacz usunie plik pid, gdy główny skrypt poprawnie zakończy pracę.

Zamiast tego, jeśli plik pid istnieje podczas uruchamiania instancji, zostanie wykonana instrukcja if zawierająca kod w drugiej instrukcji if. W takim przypadku postanowiłem uruchomić alarmową pocztę, kiedy to nastąpi.

Co jeśli skrypt się zawiesi

Kolejnym ćwiczeniem byłoby radzenie sobie z awariami. Idealnie plik pidfile powinien zostać usunięty, nawet jeśli główny skrypt zawiedzie z jakiegokolwiek powodu, nie dzieje się tak w mojej wersji powyżej. Oznacza to, że w przypadku awarii skryptu plik pid musiałby zostać ręcznie usunięty, aby przywrócić funkcjonalność.

W przypadku awarii systemu

Dobrym pomysłem jest przechowywanie pliku pidfile / lockfile na przykład / tmp. W ten sposób skrypty będą nadal działać po awarii systemu, ponieważ pliki pid zawsze będą usuwane podczas uruchamiania.


W przeciwieństwie do ansat Tima Kennedyego, twój skrypt NIE zawiera warunków wyścigu. Wynika to z tego, że sprawdzanie obecności pliku PIDFILE i jego warunkowego tworzenia nie odbywa się w operacji atomowej.
maxschlepzig

+1 na to! Rozważę to i zmodyfikuję skrypt.
ziggestardust

-2

Sprawdź mój skrypt ...

Możesz to UWIELBIAĆ ....

[rambabu@Server01 ~]$ sh Prevent_cron-OR-Script_against_parallel_run.sh
Parallel RUN Enabled
Now running
Task completed in Parallel RUN...
[rambabu@Server01 ~]$ cat Prevent_cron-OR-Script_against_parallel_run.sh
#!/bin/bash
#Created by RambabuKella
#Date : 12-12-2013

#LOCK file name
Parallel_RUN="yes"
#Parallel_RUN="no"
PS_GREP=0
LOCK=/var/tmp/mylock_`whoami`_"$0"
#Checking for the process
PS_GREP=`ps -ef |grep "sh $0" |grep -v grep|wc -l`
if [ "$Parallel_RUN" == "no" ] ;then
echo "Parallel RUN Disabled"

 if [ -f $LOCK ] || [ $PS_GREP -gt 2   ] ;then
        echo -e "\nJob is already running OR LOCK file exists. "
        echo -e "\nDetail are : "
        ps -ef |grep  "$0" |grep -v grep
        cat "$LOCK"
  exit 6
 fi
echo -e "LOCK file \" $LOCK \" created on : `date +%F-%H-%M` ." &> $LOCK
# do some work
echo "Now running"
echo "Task completed on with single RUN ..."
#done

rm -v $LOCK 2>/dev/null
exit 0
else

echo "Parallel RUN Enabled"

# do some work
echo "Now running"
echo "Task completed in Parallel RUN..."
#done

exit 0
fi
echo "some thing wrong"
exit 2
[rambabu@Server01 ~]$

-3

Oferuję następujące rozwiązanie w skrypcie o nazwie „flocktest”

#!/bin/bash
export LOGFILE=`basename $0`.logfile
logit () {
echo "$1" >>$LOGFILE
}
PROGPATH=$0
(
flock -x -n 257
(($?)) && logit "'$PROGPATH' is already running!" && exit 0
logit "'$PROGPATH', proc($$): sleeping 30 seconds"
sleep 30
)257<$PROGPATH
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.