Planen Sie den letzten Tag eines jeden Monats

10

Ich habe aus einer Anweisung gelesen, am letzten Tag des Monats ein Skript zu planen:

Hinweis:
Der kluge Leser fragt sich möglicherweise, wie Sie einen Befehl festlegen können, der am letzten Tag eines jeden Monats ausgeführt wird, da Sie den Wert für den Tag des Monats nicht so einstellen können, dass er jeden Monat abdeckt. Dieses Problem hat Linux- und Unix-Programmierer geplagt und einige verschiedene Lösungen hervorgebracht. Eine übliche Methode besteht darin, eine if-then-Anweisung hinzuzufügen, die mit dem Befehl date prüft, ob das Datum von morgen 01 ist:

00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1

Dies überprüft jeden Tag um 12 Uhr, ob es der letzte Tag des Monats ist, und wenn ja, führt cron den Befehl aus.

Geben Sie hier die Bildbeschreibung ein

Wie funktioniert das [`date +%d -d tomorrow` = 01 ]?
Ist es richtig zu sagen then; command1?

Infinitesimalrechnung
quelle
Sind Sie sicher, dass das wörtlich ist, was es sagt? Wie hier geschrieben, funktioniert es tatsächlich nicht .
Michael Homer
Ich habe den Schnappschuss gepostet. @ MichaelHomer
Calculus
Vielen Dank! Ich habe an der Formatierung herumgespielt, damit sie übereinstimmt - es ist immer noch nicht ganz richtig, aber es ist das, was das Bild sagt.
Michael Homer
1
Vermissen ; endif?
Danblack
Es funktioniert nicht. Es enthält Syntaxfehler: Kein Leerzeichen danach [und kein Leerzeichen fiam Ende. Auch %ist speziell in Crontabs.
Kusalananda

Antworten:

17

Abstrakt

Der richtige Code sollte sein:

#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"

Rufen Sie dieses Skript auf end_of_month.shund der Aufruf in cron lautet einfach:

00 12 28-31 * * /path/to/script/end_of_month.sh command

Dadurch wird das Skript end_of_month(das intern überprüft, ob der Tag der letzte Tag des Monats ist) nur an den Tagen 28, 29, 30 und 31 ausgeführt. An keinem anderen Tag muss das Monatsende überprüft werden.

Alter Beitrag.

Dies ist ein Zitat aus dem Buch "Linux Command Line and Shell Scripting Bible" von Richard Blum, Christine Bresnahan, S. 442, 3. Auflage, John Wiley & Sons © 2015.

Ja, das steht darin, aber das ist falsch / unvollständig:

  • Fehlender Abschluss fi.
  • Benötigt Platz zwischen [und den folgenden `.
  • Es wird dringend empfohlen, anstelle von $ (…) zu verwenden `…`.
  • Es ist wichtig, dass Sie Anführungszeichen für Erweiterungen wie verwenden"$(…)"
  • Es gibt ein zusätzliches ;Nachthen

Wie soll ich wissen? ( naja , aus Erfahrung ☺) aber Sie können Shellcheck ausprobieren . Fügen Sie den Code aus dem Buch ein (nach den Sternchen) und es werden Ihnen die oben aufgeführten Fehler sowie ein "fehlender Schebang" angezeigt. Ein Skript ohne Fehler in Shellcheck lautet wie folgt:

#!/bin/sh if [ "$(date +%d -d tomorrow)" = 01 ] ; then script.sh; fi

Diese Seite funktioniert, weil das, was geschrieben wurde, "Shell-Code" ist. Das ist eine Syntax, die in vielen Shells funktioniert.

Einige Probleme, die Shellcheck nicht erwähnt, sind:

  • Es wird davon ausgegangen, dass der Datumsbefehl die GNU-Datumsversion ist. Die mit einer -dOption, die tomorrowals Wert akzeptiert wird (Busybox hat die Option -d, versteht sie aber morgen nicht und BSD hat eine -dOption, bezieht sich jedoch nicht auf die "Anzeige" der Zeit).

  • Es ist besser, das Format nach allen Optionen festzulegen date -d tomorrow +'%d'.

  • Die Cron-Startzeit ist immer in der Ortszeit. Dies kann dazu führen, dass ein Job 1 Stunde früher als eine genaue Tageszählung startet, wenn die Sommerzeit eingestellt oder deaktiviert wurde.

Was wir gemacht haben, ist ein Shell-Skript, das mit cron aufgerufen werden könnte. Wir können das Skript weiter modifizieren, um Argumente des auszuführenden Programms oder Befehls wie folgt zu akzeptieren (schließlich den richtigen Code):

#!/bin/sh
[ "$#" -eq 0 ] && echo "Usage: $0 command [args]" && exit 1
[ "$(date -d tomorrow +'%d')" = 01 ] || exit 0
exec "$@"

Rufen Sie dieses Skript auf end_of_month.shund der Aufruf in cron lautet einfach:

00 12 28-31 * * /path/to/script/end_of_month.sh command

Dadurch wird das Skript end_of_month(das intern überprüft, ob der Tag der letzte Tag des Monats ist) nur an den Tagen 28, 29, 30 und 31 ausgeführt. An keinem anderen Tag muss das Monatsende überprüft werden.

Stellen Sie sicher, dass der richtige Pfad enthalten ist. Der Pfad in cron ist (wahrscheinlich) nicht derselbe wie der Benutzerpfad.

Beachten Sie, dass ein Skript zum Monatsende getestet wurde (wie unten angegeben), das viele andere Dienstprogramme oder Skripte aufrufen kann.

Dadurch wird auch das zusätzliche Problem vermieden, das cron mit der vollständigen Befehlszeile generiert:

  • Cron teilt die Befehlszeile auf jede auf, %auch wenn sie entweder mit 'oder angegeben ist "( \hier funktioniert nur a ). Dies ist eine übliche Methode, mit der Cron-Jobs fehlschlagen.

Sie können testen, ob das end_of_month.shSkript an einem bestimmten Datum ordnungsgemäß funktioniert (ohne bis zum Ende des Monats zu warten, um festzustellen, dass es nicht funktioniert), indem Sie es mit faketime testen:

$ faketime 2018/10/31 ./end_of_month echo "Command will be executed...."
Command will be executed....
NotAnUnixNazi
quelle
ast-open date(oder das dateeingebaute von ksh93, wenn ksh93 als Teil von ast-open gebaut wurde) unterstützt date -d tomorrow +%soder date +%s tomorrow).
Stéphane Chazelas
2
Die Erfahrung hat (oft) gezeigt, dass es viel besser ist, das Skript mit Faketime richtig zu testen, als bis zum Ende des Monats zu warten, um festzustellen, dass der geplante Job nicht funktioniert hat. Als würde man entdecken, dass das * * * * * echo "$(date -u +'date %c')" >>~/testfilenicht funktioniert, weil man vergessen hat, das zu zitieren \%(was schwierig zu debuggen wird, wenn nur ein Versuch pro Monat möglich ist). @Kusalananda
NotAnUnixNazi
8

Angenommen, die Syntaxfehler sind behoben und der Befehl wurde leicht umformuliert, um weniger ausführlich zu sein:

00 12 28-31 * * [ "$( date -d tomorrow +\%d )" != "01" ] || command1

Dies wird ausgeführt date +%d -d tomorrow(vorausgesetzt, es wird GNU dateverwendet), um das morgige Datum als zweistellige Zahl abzurufen. Wenn die Nummer nicht ist 01, ist heute nicht der letzte Tag des Monats. In diesem Fall sind die Tests erfolgreich und command1werden nicht ausgeführt. Der Job wird an den Tagen, die möglicherweise der letzte Tag des Monats sein könnten, mittags ausgeführt.

Der ursprüngliche Befehl:

00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command1

Dies hat einige Probleme:

  • Kein Platz danach [.
  • A ;direkt danach then.
  • %ist speziell in Cron-Jobspezifikationen und muss als \%(siehe man 5 crontab) maskiert werden .
  • Am fiEnde gibt es kein Finale , das mit dem übereinstimmt if.
Kusalananda
quelle
2
Das Problem bei der Verwendung [ ... ] && command1anstelle von if...besteht darin, dass an Tagen, die nicht der letzte Tag des Monats sind, der Cron-Job mit einem Exit-Status ungleich Null endet und dieser Fehler möglicherweise gemeldet werden muss. Die Verwendung [ "$(...)" != 01 ] || command1ist ein weiterer Weg, um das Problem zu vermeiden.
Stéphane Chazelas
1
Ein weiteres Problem mit dem Originalcode ist das ;zwischen thenund command1.
Stéphane Chazelas
@ StéphaneChazelas Ein weiterer Grund, warum ich Einzeiler nicht mag, sind sie schwer zu lesen.
Kusalananda
Der Zeitunterschied zwischen aufeinanderfolgenden Befehlsläufen beträgt möglicherweise keine ganzzahlige Anzahl von (24 Stunden) Tagen, da eine DST-Änderung die Startzeit von cron verschiebt.
NotAnUnixNazi
3
@cat Es spielt keine Rolle, wie Sie zitieren, "oder '(außer \), das Prozentzeichen lässt %cron die Linie in zwei Teile brechen. Das ist ein üblicher Weg, um Cron zum Scheitern zu bringen.
NotAnUnixNazi
-1

Um den letzten Tag eines jeden Monats zu planen, können Sie Folgendes versuchen : 0 0 15,L * *.

Kylin Sky
quelle