Führen Sie das Bash-Skript buchstäblich alle 3 Tage aus

8

Ich möchte buchstäblich alle 3 Tage ein Shell-Skript ausführen. Die Verwendung von crontab mit 01 00 */3 * *erfüllt die Bedingung nicht wirklich, da es am 31. und dann wieder am 1. Tag eines Monats ausgeführt wird. Die */3Syntax entspricht der von 1,4,7 ... 25,28,31.

Es sollte Möglichkeiten geben, das Skript selbst dazu zu bringen, die Bedingungen zu überprüfen und zu beenden, wenn 3 Tage nicht vergangen sind. Crontab führt das Skript also jeden Tag aus, aber das Skript selbst prüft, ob 3 Tage vergangen sind.

Ich habe sogar Code gefunden, aber es gab mir Syntaxfehler, jede Hilfe wäre dankbar.

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

main.sh: line 1: syntax error near unexpected token `"z"'
main.sh: line 1: `if (! (date("z") % 3)) {'
Taavi
quelle
4
Was genau meinst du? Wie geht das */3nicht "wenn 3 Tage nicht vergangen sind": drei Tage seit was? Bitte bearbeiten Sie Ihre Frage und klären Sie.
Terdon
4
buchstäblich buchstäblich? Dies scheint eine x / y-Frage zu sein, und Sie möchten vielleicht tatsächlich darüber sprechen, was Sie versuchen zu tun. Versuchen Sie zu verhindern, dass crontab das Skript ausführt?
Geselle Geek
1
Die Frage wurde bearbeitet, um zu erklären, warum die Crontab-Lösung nicht funktioniert.
Taavi
So etwas date("z") % 3 == 0würde unter einem ähnlichen Problem leiden: Die Bedingung wird für die vier Tage zwischen dem 29. Dezember und dem 3. Januar falsch sein, es sei denn, dieser Dezember war Teil eines Schaltjahres.
Rhymoid

Antworten:

12

Um ein Skript sofort abzubrechen und zu beenden, wenn die letzte Ausführung noch nicht mindestens eine bestimmte Zeit zurückliegt, können Sie diese Methode verwenden, für die eine externe Datei erforderlich ist, in der das Datum und die Uhrzeit der letzten Ausführung gespeichert sind.

Fügen Sie diese Zeilen oben in Ihr Bash-Skript ein:

#!/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:

Vergessen Sie nicht, einen aussagekräftigen Wert als datefile=festzulegen und den Wert seconds=an Ihre Bedürfnisse anzupassen ( $((60*60*24*3))ausgewertet auf 3 Tage).


Wenn Sie keine separate Datei möchten, können Sie die letzte Ausführungszeit auch im Änderungszeitstempel Ihres Skripts speichern. Das bedeutet jedoch, dass Änderungen an Ihrer Skriptdatei den 3-Zähler zurücksetzen und so behandelt werden, als ob das Skript erfolgreich ausgeführt wurde.

Um dies zu implementieren, fügen Sie das folgende Snippet oben in Ihre Skriptdatei ein:

#!/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:

Vergessen Sie auch hier nicht, den Wert von seconds=an Ihre Bedürfnisse anzupassen (ergibt $((60*60*24*3))3 Tage).

Byte Commander
quelle
Ja, das Aufzeichnen des letzten erfolgreichen Aufrufs eines bestimmten Programms erfordert eine Art externen Datenspeicher, und das Dateisystem ist dafür eine naheliegende Wahl.
Kilian Foth
Sie müssen nicht einmal das Datum speichern - berühren Sie einfach jedes Mal die Datei und überprüfen Sie den Zeitstempel.
djsmiley2kStaysInside
@ djsmiley2k Ja, du hast recht. Wenn das Risiko, dass das manuelle Ändern der Skriptdatei zum Zurücksetzen der Verzögerung führt, akzeptabel ist, können Sie auch den Änderungszeitstempel verwenden. Das habe ich meiner Antwort hinzugefügt.
Byte Commander
Dies kann leicht behoben werden, indem der aktuelle Zeitstempel in der Datei gespeichert wird (anstelle des vom Menschen lesbaren Datums), sodass Sie dann die verstrichene Zeit mit abrufen können $[ $(date +%s) - $(< lastrun) ]. Wenn das Skript einmal am Tag von cron ausgeführt wird, kann es sein, dass das erforderliche Zeitintervall etwas langsamer wird, sodass das nächste Mal keinen ganzen Tag überspringt, wenn sich die Ausführung des Skripts um einige Sekunden verzögert. Das ist jeden Tag zu überprüfen, ob 71 Stunden vergangen sind.
Ilkkachu
Diese Zauberei mit Datumsdatei hat tatsächlich funktioniert, vielen Dank!
Taavi
12

Cron ist wirklich das falsche Werkzeug dafür. Es gibt tatsächlich ein häufig verwendetes und underloved Tool namens an der Macht zu arbeiten. at ist hauptsächlich für den interaktiven Gebrauch konzipiert und ich bin sicher, dass jemand einen besseren Weg finden würde, dies zu tun.

In meinem Fall würde ich das Skript, das ich ausführe, in testjobs.txt auflisten und eine Zeile einfügen, die lautet.

Als Beispiel hätte ich dies als testjobs.txt

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

Ich habe zwei unschuldige Befehle, die Ihre Muscheln sein könnten. Ich führe Echo aus, um sicherzustellen, dass ich eine deterministische Ausgabe habe, und Datum, um zu bestätigen, dass der Befehl nach Bedarf ausgeführt wird. Wenn at diesen Befehl ausführt, wird es beendet, indem 3 Tage lang ein neuer Job zu at hinzugefügt wird. (Ich habe mit einer Minute getestet - was funktioniert)

Ich bin mir ziemlich sicher, dass ich wegen der Art und Weise, wie ich missbraucht habe, gerufen werde, aber es ist ein praktisches Tool, um einen Befehl so zu planen, dass er zu einem Zeitpunkt oder x Tage nach einem vorherigen Befehl ausgeführt wird.

Geselle Geek
quelle
3
+1, wie es athier der bessere Ansatz zu sein scheint. Das Problem bei diesem Ansatz ist, dass Sie jedes Mal, wenn Sie das Skript manuell ausführen, einen weiteren Satz von atAufgaben für jeden dritten Tag hinzufügen .
Dewi Morgan
1
+1, aber ein weiteres Problem (zusätzlich zu dem, auf das @DewiMorgan hinweist) ist, dass, wenn ein Skript fehlschlägt, nicht alle nachfolgenden Skripte gestartet werden (wenn das at nach dem Fehlerpunkt liegt), bis man merkt, dass das Skript dies getan hat fehlgeschlagen und neu starten. Dies kann schlecht oder manchmal auch gut sein (z. B. schlägt fehl, weil eine Bedingung nicht mehr vorliegt: Ist es gut, dass es in 3 Tagen nicht erneut versucht wird?). Und es gibt jedes Mal eine leichte Abweichung (einige Millisekunden, wenn ates oben ist, oder möglicherweise viel mehr, wenn ates sich in der Nähe des unteren Randes eines Skripts mit langer Ausführung befindet)
Olivier Dulac
At kann E-Mails senden .... Das könnte eine Lösung für Fehler sein. atq und atrm würden es vielleicht ermöglichen, fehlerhafte Jobs auszusortieren? Theoretisch könnte man ein bestimmtes Datum für at schreiben, aber das scheint unelegant und griffig zu sein.
Geselle Geek
Haben Sie also einen Cronjob, der prüft, ob der nächste Einlauf bei möglich ist. Wenn es keine gibt, fügen Sie eine für 3 Tage hinzu und warnen Sie (oder führen Sie sie sofort aus und überprüfen Sie sie erneut).
djsmiley2kStaysInside
3

Erstens ist das obige Codefragment eine ungültige Bash-Syntax und sieht aus wie Perl. Zweitens bewirkt der zParameter, datedass er die numerische Zeitzone ausgibt. +%jist die Tagesnummer. Du brauchst:

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

Aber zum Jahresende werden Sie immer noch Fremdheit erleben:

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

Möglicherweise haben Sie besseres Glück, wenn Sie eine Zählung in einer Datei führen und diese testen / aktualisieren.

Walzer
quelle
1
Perl hat keine date()eingebaute Funktion, aber das sieht ein bisschen aus wie date () in PHP (wo zist "Der Tag des Jahres (ab 0)")
ilkkachu
2

Wenn Sie das Skript einfach ständig laufen lassen können, können Sie Folgendes tun:

while true; do

[inert code here]

sleep 259200
done

Diese Schleife ist immer wahr, führt also immer den Code aus und wartet dann drei Tage, bevor die Schleife erneut gestartet wird.

mkingsbu
quelle
Warum nicht while true?
Jonathan Leffler
Hah! Guter Fang. Ich musste das schon lange nicht mehr benutzen, ich habe vergessen, dass es existiert
mkingsbu
2
Als atLösung wird dies bei jedem Lauf um die Ausführungszeit des Skripts verschoben. Dies kann natürlich umgangen werden, indem Sie die Zeit sparen, in der das Skript gestartet wird, und bis zu 3 Tage schlafen.
Ilkkachu
2

Sie können Anacron anstelle von Cron verwenden. Es ist genau darauf ausgelegt, das zu tun, was Sie brauchen. Aus der Manpage:

Anacron kann verwendet werden, um Befehle regelmäßig mit einer in Tagen angegebenen Häufigkeit auszuführen. Im Gegensatz zu cron (8) wird nicht davon ausgegangen, dass die Maschine kontinuierlich läuft. Daher kann es auf Maschinen verwendet werden, die nicht 24 Stunden am Tag ausgeführt werden, um tägliche, wöchentliche und monatliche Jobs zu steuern, die normalerweise von cron gesteuert werden.

Bei der Ausführung liest Anacron eine Liste von Jobs aus einer Konfigurationsdatei, normalerweise / etc / anacrontab (siehe anacrontab (5)). Diese Datei enthält die Liste der Jobs, die Anacron steuert. Jeder Jobeintrag gibt einen Zeitraum in Tagen, eine Verzögerung in Minuten, eine eindeutige Jobkennung und einen Shell-Befehl an.

Für jeden Job prüft Anacron, ob dieser Job in den letzten n Tagen ausgeführt wurde, wobei n der für diesen Job angegebene Zeitraum ist. Wenn nicht, führt Anacron den Shell-Befehl des Jobs aus, nachdem auf die als Verzögerungsparameter angegebene Anzahl von Minuten gewartet wurde.

Nach dem Beenden des Befehls zeichnet Anacron das Datum in einer speziellen Zeitstempeldatei für diesen Job auf, damit es weiß, wann es erneut ausgeführt werden muss. Für die Zeitberechnung wird nur das Datum verwendet. Die Stunde wird nicht verwendet.

Funkeln
quelle
Schön, ich werde Anacron sicher testen. Ich frage mich, warum es so unterschätzt wird, wenn es so magisch wirken kann
Taavi
Die eigentliche Frage: Warum wurde Cron nicht durch etwas Besseres ersetzt? fcron existiert und ich denke, systemd arbeitet an einer eigenen Lösung, aber ich bin nicht auf dem neuesten Stand der Dinge.
Twinkles