Planifiez le dernier jour de chaque mois

10

J'ai lu une instruction pour planifier un script le dernier jour du mois:

Remarque:
Le lecteur astucieux se demande peut-être comment définir une commande à exécuter le dernier jour de chaque mois, car vous ne pouvez pas définir la valeur dayofmonth pour couvrir chaque mois. Ce problème a tourmenté les programmeurs Linux et Unix, et a engendré plusieurs solutions différentes. Une méthode courante consiste à ajouter une instruction if-then qui utilise la commande date pour vérifier si la date de demain est 01:

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

Ceci vérifie chaque jour à midi pour voir si c'est le dernier jour du mois, et si c'est le cas, cron exécute la commande.

entrez la description de l'image ici

Comment ça [`date +%d -d tomorrow` = 01 ]marche?
Est-il exact de dire then; command1?

Calcul
la source
Etes-vous sûr que c'est mot pour mot ce qu'il dit? Comme écrit ici, cela ne fonctionne pas .
Michael Homer
J'ai posté l'instantané. @ MichaelHomer
Calculus
Merci! J'ai tripoté la mise en forme pour la faire correspondre - ce n'est toujours pas tout à fait vrai, mais c'est ce que dit l'image.
Michael Homer
1
Manquant ; endif?
danblack
Ça ne marche pas. Il contient des erreurs de syntaxe: pas d'espace après [et pas fià la fin. Aussi, %est spécial dans les crontabs.
Kusalananda

Réponses:

17

Abstrait

Le code correct doit être:

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

Appelez ce script end_of_month.shet l'appel en cron est simplement:

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

Cela exécuterait le script end_of_month(qui vérifiera en interne que le jour est le dernier jour du mois) uniquement les jours 28, 29, 30 et 31. Il n'est pas nécessaire de vérifier la fin du mois un autre jour.

Ancien poste.

Il s'agit d'une citation tirée du livre «Linux Command Line and Shell Scripting Bible» de Richard Blum, Christine Bresnahan pp 442, troisième édition, John Wiley & Sons © 2015.

Oui, c'est ce qu'il dit, mais c'est faux / incomplet:

  • Manque une fermeture fi.
  • Besoin d'espace entre [les éléments suivants `.
  • Il est fortement recommandé d'utiliser $ (…) au lieu de `…`.
  • Il est important que vous utilisiez des guillemets autour des extensions comme"$(…)"
  • Il y a un supplément ;aprèsthen

Comment puis-je savoir? (enfin, par expérience ☺) mais vous pouvez essayer Shellcheck . Collez le code du livre (après les astérisques) et il vous montrera les erreurs listées ci-dessus plus un "shebang manquant". Voici un script sans erreur dans Shellcheck:

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

Ce site fonctionne parce que ce qui a été écrit est du "code shell". C'est une syntaxe qui fonctionne dans de nombreux shells.

Certains problèmes que shellcheck ne mentionne pas sont:

  • Il suppose que la commande date est la version de date GNU. Celui avec une -doption qui accepte tomorrowcomme valeur (busybox a une option -d mais ne comprend pas demain et BSD a une -doption mais n'est pas liée à "l'affichage" de l'heure).

  • Il est préférable de définir le format après toutes les options date -d tomorrow +'%d'.

  • L'heure de début cron est toujours en heure locale, ce qui peut faire démarrer une tâche 1 heure plus tôt ou plus tard qu'un compte de jour exact si l'heure d'été (DST) est réglée ou non.

Ce que nous avons fait, c'est un script shell qui pourrait être appelé avec cron. Nous pouvons encore modifier le script pour accepter les arguments du programme ou de la commande à exécuter, comme ceci (enfin, le code correct):

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

Appelez ce script end_of_month.shet l'appel en cron est simplement:

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

Cela exécuterait le script end_of_month(qui vérifiera en interne que le jour est le dernier jour du mois) uniquement les jours 28, 29, 30 et 31. Il n'est pas nécessaire de vérifier la fin du mois un autre jour.

Assurez-vous que le chemin correct est inclus. Le PATH à l'intérieur de cron ne sera pas (probablement) le même que le PATH utilisateur.

Notez qu'il existe un script de fin de mois testé (comme indiqué ci-dessous) qui pourrait appeler de nombreux autres utilitaires ou scripts.

Cela évitera également le problème supplémentaire que cron génère avec la ligne de commande complète:

  • Cron fractionne la ligne de commande sur n'importe quel %fichier même s'il est cité avec 'ou "(seul un \fonctionne ici). C'est une manière courante d'échouer dans les tâches cron.

Vous pouvez tester si le end_of_month.shscript fonctionne correctement à une certaine date (sans attendre la fin du mois pour découvrir qu'il ne fonctionne pas) en le testant avec faketime:

$ faketime 2018/10/31 ./end_of_month echo "Command will be executed...."
Command will be executed....
Isaac
la source
ast-open date(ou le datebuildin de ksh93 si ksh93 a été construit dans le cadre de ast-open) supporte date -d tomorrow +%sou date +%s tomorrow).
Stéphane Chazelas
2
L'expérience a prouvé (plusieurs fois) qu'il est beaucoup mieux de tester le script correctement exécuté avec faketime que d'attendre jusqu'à la fin du mois pour constater que le travail planifié n'a pas fonctionné. Comme découvrir que cela * * * * * echo "$(date -u +'date %c')" >>~/testfilene fonctionnera pas parce que l'on a oublié de citer le \%(qui devient difficile à déboguer si un seul essai par mois est possible). @Kusalananda
Isaac
8

En supposant que les erreurs de syntaxe sont fixes et que la commande a été reformulée légèrement pour être moins verbeuse:

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

Cela s'exécute date +%d -d tomorrow(en supposant que c'est GNU datequi est utilisé) pour obtenir la date de demain sous la forme d'un nombre à deux chiffres. Si le numéro est 01, alors aujourd'hui est pas le dernier jour du mois. Dans ce cas, les tests réussissent et necommand1 sont pas exécutés. Le travail est exécuté à midi les jours qui pourraient éventuellement être le dernier jour du mois.

La commande d'origine:

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

Cela a quelques problèmes:

  • Pas d'espace après [.
  • A ;directement après then.
  • %est spécial dans les spécifications des tâches cron et doit être échappé comme \%(voir man 5 crontab).
  • Il n'y a pas de finale fià la fin qui corresponde à la if.
Kusalananda
la source
2
Le problème avec l'utilisation [ ... ] && command1au lieu de if...est que les jours qui ne sont pas le dernier jour du mois, la tâche cron se terminera avec un état de sortie différent de zéro et cette défaillance devra peut-être être signalée. L'utilisation [ "$(...)" != 01 ] || command1est une autre façon d'éviter le problème.
Stéphane Chazelas
1
Un autre problème avec le code d'origine est ;entre thenet command1.
Stéphane Chazelas
@ StéphaneChazelas Une autre raison pour laquelle je n'aime pas les one-liners, ils sont difficiles à lire.
Kusalananda
La différence de temps entre des exécutions consécutives de la commande peut ne pas être un nombre entier de (24 heures) jours car un changement d'heure d'été changera l'heure de début de cron.
Isaac
3
@cat Peu importe comment vous citez, que ce soit "ou '(sauf \), le signe de pourcentage %fera que cron coupera la ligne en deux parties. C'est une façon habituelle de faire échouer cron.
Isaac
-1

Pour planifier le dernier jour de chaque mois, vous pouvez essayer: 0 0 15,L * *.

Kylin Sky
la source