Exécuter le script bash littéralement tous les 3 jours

8

Je veux exécuter un script shell littéralement tous les 3 jours. L'utilisation de crontab avec 01 00 */3 * *ne remplira pas réellement la condition, car elle s'exécuterait le 31 puis le 1er jour d'un mois. La */3syntaxe est la même que pour dire 1,4,7 ... 25,28,31.

Il devrait y avoir des moyens pour que le script lui-même vérifie les conditions et quitte si 3 jours ne se sont pas écoulés. Donc crontab exécute le script tous les jours, mais le script lui-même vérifierait si 3 jours se sont écoulés.

J'ai même trouvé du code, mais cela m'a donné une erreur de syntaxe, toute aide serait appréciée.

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

main.sh: line 1: syntax error near unexpected token `"z"'
main.sh: line 1: `if (! (date("z") % 3)) {'
Taavi
la source
4
Que voulez-vous dire exactement? Comment ça */3ne marche pas? "si 3 jours ne se sont pas écoulés": trois jours depuis quoi? Veuillez modifier votre question et clarifier.
terdon
4
littéralement littéralement? Cela semble être une question x / y et vous voudrez peut-être parler de ce que vous essayez de faire. Essayez-vous d'empêcher crontab d'exécuter le script?
Journeyman Geek
1
Modification de la question pour expliquer pourquoi la solution crontab ne fonctionnera pas.
Taavi
Quelque chose comme date("z") % 3 == 0souffrirait d'un problème similaire: la condition sera fausse pendant les quatre jours entre le 29 décembre et le 3 janvier, à moins que ce décembre ne fasse partie d'une année bissextile.
Rhymoid

Réponses:

12

Pour abandonner et quitter immédiatement un script si la dernière exécution ne remonte pas à au moins une heure spécifique, vous pouvez utiliser cette méthode qui nécessite un fichier externe qui stocke la dernière date et heure d'exécution.

Ajoutez ces lignes en haut de votre script 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:

N'oubliez pas de définir une valeur significative datefile=et d'adapter la valeur de seconds=à vos besoins ( $((60*60*24*3))évaluation à 3 jours).


Si vous ne voulez pas de fichier séparé, vous pouvez également stocker la dernière heure d'exécution dans l'horodatage de modification de votre script. Cela signifie cependant que toute modification de votre fichier de script réinitialisera le compteur 3 et sera traitée comme si le script était en cours d'exécution.

Pour implémenter cela, ajoutez l'extrait ci-dessous en haut de votre fichier de script:

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

Encore une fois, n'oubliez pas d'adapter la valeur de seconds=à vos besoins ( $((60*60*24*3))évalue à 3 jours).

Byte Commander
la source
Oui, l'enregistrement de la dernière invocation réussie d'un programme particulier nécessite une sorte de stockage de données externe, et le système de fichiers est un choix évident pour cela.
Kilian Foth
Vous n'avez même pas besoin de stocker la date - touchez simplement le fichier à chaque fois et vérifiez son horodatage.
djsmiley2kStaysInside
@ djsmiley2k Oui, vous avez raison. Si le risque que la modification manuelle du fichier de script entraîne la réinitialisation du délai est acceptable, on peut également utiliser l'horodatage de modification. J'ai ajouté cela à ma réponse.
Byte Commander
Cela peut être légèrement joué en enregistrant l'horodatage actuel dans le fichier (au lieu de la date lisible par l'homme), afin que vous puissiez ensuite obtenir le temps écoulé avec $[ $(date +%s) - $(< lastrun) ]. Si le script est exécuté depuis cron une fois par jour, je pourrais ajouter un peu de mou à l'intervalle de temps requis, de sorte que si l'exécution du script est retardée de quelques secondes, la prochaine fois ne sautera pas une journée entière. C'est vérifier chaque jour si 71 heures se sont écoulées.
ilkkachu
Cette magie avec le fichier de date a réellement fonctionné, merci beaucoup!
Taavi
12

Cron n'est vraiment pas le bon outil pour cela. Il y a en fait un outil couramment utilisé et underloved appelé à laquelle le travail de force. at est principalement conçu pour une utilisation interactive et je suis sûr que quelqu'un trouverait une meilleure façon de le faire.

Dans mon cas, j'aurais le script que j'exécute répertorié dans testjobs.txt, et inclure une ligne qui lit.

Par exemple, j'aurais ceci comme testjobs.txt

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

J'ai deux ordres innocents, qui pourraient être vos scripts d'obus. J'exécute echo pour m'assurer d'avoir une sortie déterministe et une date pour confirmer que la commande s'exécute selon les besoins. Lorsque at exécute cette commande, elle se termine en ajoutant un nouveau travail à at pendant 3 jours. (J'ai testé avec une minute - ce qui fonctionne)

Je suis sûr que je serai appelé pour la manière dont j'ai abusé, mais c'est un outil pratique pour planifier une commande à exécuter à un moment ou x jours après une commande précédente.

Compagnon Geek
la source
3
+1, comme atsemble la meilleure approche ici.Le problème avec cette approche est que chaque fois que vous exécutez manuellement le script, vous ajoutez un autre ensemble de attâches tous les 3 jours .
Dewi Morgan
1
+1, mais un autre problème (en plus de celui signalé par @DewiMorgan) est que si un script échoue, tous les scripts suivants ne seront pas lancés (si at se trouve après le point de défaillance), jusqu'à ce que l'on se rende compte que le script a échoué et le relancer. Cela peut être mauvais ou parfois bon (ex: échoue car une condition n'est plus là: c'est bien qu'elle ne réessaye pas dans 3 jours?). Et il y a une légère dérive à chaque fois (quelques millisecondes si atest en haut, ou potentiellement beaucoup plus si atest près du bas d'un script à longue exécution)
Olivier Dulac
À peut envoyer des e-mails .... Ce qui pourrait être une solution à l'échec. atq et atrm permettraient peut-être de supprimer les travaux errants? En théorie, vous pouvez écrire une date spécifique pour mais cela semble inélégant et pratique.
Journeyman Geek
Ayez donc un cronjob qui vérifie l'existence de la prochaine exécution dans at; s'il n'y en a pas, ajoutez-en un pendant 3 jours et avertissez (ou exécutez-le immédiatement et vérifiez à nouveau).
djsmiley2kStaysInside
3

Tout d'abord, le fragment de code ci-dessus n'est pas une syntaxe Bash non valide, ressemble à Perl. Deuxièmement, le zparamètre le datefait sortir le fuseau horaire numérique. +%jest le numéro du jour. Vous avez besoin:

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

Mais vous allez toujours voir l'étrangeté à la fin de l'année:

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

Vous pourriez avoir plus de chance de conserver un décompte dans un fichier et de le tester / le mettre à jour.

waltinator
la source
1
Perl n'a pas de date()fonction intégrée, mais cela ressemble un peu à date () en PHP (où zest "Le jour de l'année (à partir de 0)")
ilkkachu
2

Si vous pouvez simplement laisser le script s'exécuter perpétuellement, vous pouvez le faire:

while true; do

[inert code here]

sleep 259200
done

Cette boucle est toujours vraie, elle exécutera donc toujours le code, puis attendra trois jours avant de recommencer la boucle.

mkingsbu
la source
Pourquoi ne pas while true?
Jonathan Leffler
Hah! Bonne prise. Je n'ai pas eu besoin de l'utiliser depuis longtemps, j'ai oublié qu'il existait lol
mkingsbu
2
Comme atsolution, cela dérivera par le temps d'exécution du script à chaque exécution. Bien sûr, cela peut être contourné en économisant le moment où le script démarre et en dormant jusqu'à 3 jours à partir de cela.
ilkkachu
2

Vous pouvez utiliser anacron au lieu de cron, il est conçu exactement pour faire ce dont vous avez besoin. Depuis la page de manuel:

Anacron peut être utilisé pour exécuter des commandes périodiquement, avec une fréquence spécifiée en jours. Contrairement à cron (8), il ne suppose pas que la machine fonctionne en continu. Par conséquent, il peut être utilisé sur des machines qui ne fonctionnent pas 24 heures sur 24, pour contrôler les tâches quotidiennes, hebdomadaires et mensuelles qui sont généralement contrôlées par cron.

Une fois exécuté, Anacron lit une liste de travaux à partir d'un fichier de configuration, normalement / etc / anacrontab (voir anacrontab (5)). Ce fichier contient la liste des travaux contrôlés par Anacron. Chaque entrée de travail spécifie une période en jours, un délai en minutes, un identifiant de travail unique et une commande shell.

Pour chaque travail, Anacron vérifie si ce travail a été exécuté au cours des n derniers jours, où n est la période spécifiée pour ce travail. Sinon, Anacron exécute la commande shell du travail, après avoir attendu le nombre de minutes spécifié comme paramètre de délai.

Une fois la commande terminée, Anacron enregistre la date dans un fichier d'horodatage spécial pour ce travail, afin qu'il puisse savoir quand l'exécuter à nouveau. Seule la date est utilisée pour les calculs d'heure. L'heure n'est pas utilisée.

Scintille
la source
Bien, je vais certainement tester Anacron. Je
me
La vraie question: pourquoi cron n'a-t-il pas été remplacé par quelque chose de mieux maintenant? fcron existe et je pense que systemd travaille sur sa propre solution, mais je ne suis pas au courant de la situation actuelle.
Twinkles