Wykonuj skrypt bash dosłownie co 3 dni

8

Chcę wykonywać skrypt powłoki dosłownie co 3 dni. Używanie crontab z 01 00 */3 * *tak naprawdę nie spełnia warunku, ponieważ działałoby 31, a następnie ponownie pierwszego dnia miesiąca. */3Składnia jest taka sama jak mówią 1,4,7 ... 25,28,31.

Powinny istnieć sposoby, aby sam skrypt sprawdził warunki i zakończył działanie, jeśli nie minęły 3 dni. Więc crontab wykonuje skrypt codziennie, ale sam skrypt sprawdzi, czy minęły 3 dni.

Znalazłem nawet trochę kodu, ale dał mi błąd składniowy, każda pomoc byłaby mile widziana.

if (! (date("z") % 3)) {
     exit;
}

main.sh: line 1: syntax error near unexpected token `"z"'
main.sh: line 1: `if (! (date("z") % 3)) {'
Taavi
źródło
4
Co miałeś dokładnie na myśli? Jak to */3nie działa „jeśli nie minęły 3 dni”: trzy dni od czego? Proszę edytować swoje pytanie i wyjaśnienia.
terdon
4
dosłownie dosłownie? To wydaje się być pytaniem x / y i możesz chcieć rozmawiać o tym, co próbujesz zrobić. Czy próbujesz powstrzymać crontab przed uruchomieniem skryptu?
Journeyman Geek
1
Edytowałem pytanie, aby wyjaśnić, dlaczego rozwiązanie crontab nie działa.
Taavi,
Coś podobnego date("z") % 3 == 0cierpiałoby na podobny problem: warunek będzie fałszywy przez cztery dni między 29 grudnia a 3 stycznia, chyba że grudzień był rokiem przestępnym.
Rhymoid,

Odpowiedzi:

12

Aby natychmiast przerwać wykonywanie skryptu i wyjść z niego, jeśli ostatnie wykonanie nie było jeszcze w określonym momencie, możesz użyć tej metody, która wymaga zewnętrznego pliku, który przechowuje datę i godzinę ostatniego wykonania.

Dodaj następujące wiersze u góry skryptu Bash:

#!/bin/bash

# File that stores the last execution date in plain text:
datefile=/path/to/your/datefile

# Minimum delay between two script executions, in seconds. 
seconds=$((60*60*24*3))

# Test if datefile exists and compare the difference between the stored date 
# and now with the given minimum delay in seconds. 
# Exit with error code 1 if the minimum delay is not exceeded yet.
if test -f "$datefile" ; then
    if test "$(($(date "+%s")-$(date -f "$datefile" "+%s")))" -lt "$seconds" ; then
        echo "This script may not yet be started again."
        exit 1
    fi
fi

# Store the current date and time in datefile
date -R > "$datefile"

# Insert your normal script here:

Nie zapomnij ustawić znaczącej wartości jako datefile=i dostosować wartość seconds=do swoich potrzeb ( $((60*60*24*3))szacuje się na 3 dni).


Jeśli nie chcesz osobnego pliku, możesz również zapisać czas ostatniego wykonania w znaczniku czasu modyfikacji skryptu. Oznacza to jednak, że wprowadzenie jakichkolwiek zmian w pliku skryptu spowoduje zresetowanie licznika 3 i będzie traktowane tak, jakby skrypt działał poprawnie.

Aby to zaimplementować, dodaj poniższy fragment kodu na górze pliku skryptu:

#!/bin/bash

# Minimum delay between two script executions, in seconds. 
seconds=$((60*60*24*3))

# Compare the difference between this script's modification time stamp 
# and the current date with the given minimum delay in seconds. 
# Exit with error code 1 if the minimum delay is not exceeded yet.
if test "$(($(date "+%s")-$(date -r "$0" "+%s")))" -lt "$seconds" ; then
    echo "This script may not yet be started again."
    exit 1
fi

# Store the current date as modification time stamp of this script file
touch -m -- "$0"

# Insert your normal script here:

Ponownie nie zapomnij dostosować wartości seconds=do swoich potrzeb ( $((60*60*24*3))ocenia do 3 dni).

Bajt Dowódca
źródło
Tak, nagranie ostatniego pomyślnego wywołania określonego programu wymaga pewnego rodzaju zewnętrznego magazynu danych, a system plików jest oczywistym wyborem.
Kilian Foth,
Nie musisz nawet przechowywać daty - wystarczy dotknąć pliku za każdym razem i sprawdzić jego znacznik czasu.
djsmiley2kStaysInside
@ djsmiley2k Tak, masz rację. Jeśli ryzyko, że modyfikowanie pliku skryptu ręcznie spowoduje opóźnienie resetu, jest dopuszczalne, można również użyć znacznika czasu modyfikacji. Dodałem to do mojej odpowiedzi.
Bajt Dowódca
Można to nieznacznie zmienić w golfa, zapisując bieżący znacznik czasu w pliku (zamiast daty czytelnej dla człowieka), dzięki czemu można uzyskać upływ czasu $[ $(date +%s) - $(< lastrun) ]. Jeśli skrypt jest uruchamiany z crona raz dziennie, mogę dodać trochę luzu do wymaganego przedziału czasowego, aby jeśli wykonanie skryptu opóźniło się o kilka sekund, następnym razem nie pominie całego dnia. To jest sprawdzanie każdego dnia, czy 71 godzin minęło.
ilkkachu
Ta magia z plikiem daty faktycznie działała, dziękuję bardzo!
Taavi
12

Cron jest naprawdę niewłaściwym narzędziem do tego. Jest rzeczywiście powszechnie stosowane i underloved narzędzie o nazwie na które działają siły. at został zaprojektowany głównie do użytku interaktywnego i jestem pewien, że ktoś znalazłby lepszy sposób na zrobienie tego.

W moim przypadku skrypt, który uruchamiam, powinien znajdować się na liście w testjobs.txt i zawierać wiersz, który czyta.

Jako przykład chciałbym mieć to jako testjobs.txt

echo "cat" >> foo.txt
date >> foo.txt
at now + 3 days < testjobs.txt

Mam dwa niewinne polecenia, które mogą być twoimi skryptami. Uruchomiam echo, aby upewnić się, że mam wynik deterministyczny, i datę, aby potwierdzić, że polecenie zostanie uruchomione w razie potrzeby. Po uruchomieniu tej komendy at zakończy działanie, dodając nowe zadanie do at na 3 dni. (Testowałem z jedną minutą - co działa)

Jestem prawie pewien, że zostanę wezwany ze względu na sposób, w jaki byłem nadużywany, ale jest to przydatne narzędzie do planowania uruchomienia polecenia w czasie lub x dni po poprzednim poleceniu.

Journeyman Geek
źródło
3
+1 atwydaje się tutaj lepszym podejściem. Problem z tym podejściem polega na tym, że za każdym razem, gdy ręcznie uruchamiasz skrypt, dodajesz kolejny zestaw zadań co 3 dni at.
Dewi Morgan
1
+1, ale inną kwestią (oprócz tej wskazywanej przez @DewiMorgan) jest to, że jeśli jeden skrypt zawiedzie, wszystkie kolejne skrypty nie zostaną uruchomione (jeśli at jest po punkcie awarii), dopóki nie zorientujemy się, że skrypt miał nie powiodło się i uruchom go ponownie. Może to być złe, a czasem dobre (np. Zawodzi, ponieważ warunek już nie istnieje: dobrze, że nie próbuje się ponownie za 3 dni?). I za każdym razem występuje niewielki dryf (kilka milisekund, jeśli atjest na górze, lub potencjalnie znacznie więcej, jeśli atjest blisko dolnej części skryptu o długim wykonaniu)
Olivier Dulac
At może wysyłać wiadomości e-mail .... Co może być rozwiązaniem niepowodzenia. atq i atrm pozwolą na wyeliminowanie błędnych zadań? Teoretycznie możesz napisać konkretną datę na, ale wydaje się to nieeleganckie i wygodne.
Journeyman Geek
Więc mieć cronjob, który sprawdza istnienie następnego uruchomienia w; jeśli nie ma, dodaj jeden na 3 dni i ostrzegaj (lub uruchom go od razu i sprawdź ponownie).
djsmiley2kStaysInside
3

Po pierwsze, powyższy fragment kodu jest niepoprawną składnią Bash, wygląda jak Perl. Po drugie, zparametr datepowodujący wyświetlanie numerycznej strefy czasowej. +%jto numer dnia. Potrzebujesz:

if [[ ! $(( $(date +%j) % 3 )) ]] ;then
     exit
fi

Ale pod koniec roku nadal będziesz widział dziwność:

$ for i in 364 365  1 ; do echo "Day $i, $(( $i % 3 ))"; done
Day 364, 1
Day 365, 2
Day 1, 1

Możesz mieć więcej szczęścia z utrzymywaniem liczby w pliku i testowaniem / aktualizowaniem tego.

waltinator
źródło
1
Perl nie ma date()wbudowanej funkcji, ale wygląda to trochę jak date () w PHP (gdzie zjest „Dzień roku (od 0)”)
ilkkachu 26.09.16
2

Jeśli możesz po prostu pozostawić skrypt działający bez końca, możesz:

while true; do

[inert code here]

sleep 259200
done

Ta pętla jest zawsze prawdziwa, więc zawsze wykona kod, a następnie zaczeka trzy dni przed ponownym uruchomieniem pętli.

mkingsbu
źródło
Dlaczego nie while true?
Jonathan Leffler,
Hah! Dobry chwyt Dawno nie musiałem z tego korzystać, zapomniałem, że istnieje lol
mkingsbu 25.09.16
2
Jako atrozwiązanie będzie dryfować według czasu wykonania skryptu przy każdym uruchomieniu. Oczywiście można to obejść, oszczędzając czas, kiedy skrypt zaczyna się i śpi, do 3 dni od tego.
ilkkachu
2

Możesz użyć anakronu zamiast crona, jest on zaprojektowany dokładnie do robienia tego, czego potrzebujesz. Z strony podręcznika:

Anacron może być używany do okresowego wykonywania poleceń, z częstotliwością określoną w dniach. W przeciwieństwie do cron (8), nie zakłada, że ​​maszyna działa w sposób ciągły. Dlatego można go używać na komputerach, które nie działają 24 godziny na dobę, do kontrolowania codziennych, tygodniowych i miesięcznych zadań, które są zwykle kontrolowane przez cron.

Po uruchomieniu Anacron czyta listę zadań z pliku konfiguracyjnego, zwykle / etc / anacrontab (patrz anacrontab (5)). Ten plik zawiera listę zadań kontrolowanych przez Anacron. Każdy wpis zadania określa okres w dniach, opóźnienie w minutach, unikalny identyfikator zadania i polecenie powłoki.

Dla każdego zadania Anacron sprawdza, czy to zadanie zostało wykonane w ciągu ostatnich n dni, gdzie n jest okresem określonym dla tego zadania. Jeśli nie, Anacron uruchamia polecenie powłoki zadania po odczekaniu liczby minut określonej jako parametr opóźnienia.

Po wyjściu polecenia Anacron zapisuje datę w specjalnym pliku znacznika czasu dla tego zadania, dzięki czemu może wiedzieć, kiedy wykonać go ponownie. Do obliczeń czasu używana jest tylko data. Godzina nie jest używana.

Migoczą
źródło
Fajnie, na pewno przetestuję Anacrona. Zastanawiam się, dlaczego jest tak niedoceniany, jeśli potrafi dokonywać takiej magii
Taavi
Prawdziwe pytanie: dlaczego cron nie został zastąpiony czymś lepszym? fcron istnieje i myślę, że systemd pracuje nad własnym rozwiązaniem, ale nie jestem na bieżąco z obecnym stanem rzeczy.
Twinkles,