Tâche CRON à exécuter le dernier jour du mois

95

J'ai besoin de créer un travail CRON qui s'exécutera le dernier jour de chaque mois. Je vais le créer en utilisant cPanel.

Toute aide est appréciée. Merci

Utku Dalmaz
la source

Réponses:

180

Le moyen le plus simple est peut-être d'effectuer trois tâches distinctes:

55 23 30 4,6,9,11        * myjob.sh
55 23 31 1,3,5,7,8,10,12 * myjob.sh
55 23 28 2               * myjob.sh

Cela se déroulera le 28 février, même les années bissextiles, donc si cela pose un problème, vous devrez trouver un autre moyen.


Cependant, il est généralement à la fois beaucoup plus facile et correct d'exécuter le travail dès que possible le premier jour de chaque mois, avec quelque chose comme:

0 0 1 * * myjob.sh

et modifiez le script pour traiter les données du mois précédent .

Cela supprime tous les tracas que vous pourriez rencontrer pour déterminer quel jour est le dernier du mois, et garantit également que toutes les données pour ce mois sont disponibles, en supposant que vous traitez des données. Courir entre cinq minutes et minuit le dernier jour du mois peut vous voir manquer tout ce qui se passe entre ce moment et minuit.

C'est la façon habituelle de le faire de toute façon, pour la plupart des emplois de fin de mois.


Si vous voulez vraiment l' exécuter le dernier jour du mois, une option consiste simplement à détecter si demain est le premier (soit dans le cadre de votre script, soit dans le crontab lui-même).

Donc, quelque chose comme:

55 23 28-31 * * [[ "$(date --date=tomorrow +\%d)" == "01" ]] && myjob.sh

devrait être un bon début, en supposant que vous ayez un dateprogramme relativement intelligent .

Si votre dateprogramme n'est pas assez avancé pour vous donner des dates relatives, vous pouvez simplement créer un programme très simple pour vous donner le jour du mois de demain (vous n'avez pas besoin de toute la puissance de date), tel que:

#include <stdio.h>
#include <time.h>

int main (void) {
    // Get today, somewhere around midday (no DST issues).

    time_t noonish = time (0);
    struct tm *localtm = localtime (&noonish);
    localtm->tm_hour = 12;

    // Add one day (86,400 seconds).

    noonish = mktime (localtm) + 86400;
    localtm = localtime (&noonish);

    // Output just day of month.

    printf ("%d\n", localtm->tm_mday);

    return 0;
}

puis utilisez (en supposant que vous l'avez appelé tomdompour "le jour du mois de demain"):

55 23 28-31 * * [[ "$(tomdom)" == "1" ]] && myjob.sh

Bien que vous voudrez peut - être envisager d' ajouter un contrôle d'erreur puisque les deux time()et mktime()peut revenir -1si quelque chose se passe mal. Le code ci-dessus, pour des raisons de simplicité, n'en tient pas compte.

paxdiablo
la source
7
ouais, il peut être plus facile de courir tous les premiers jours au lieu du dernier jour :)
Utku Dalmaz
1
1er jour du mois en effet. Voici à quoi ressemblera le code en PHP $ date = new DateTime ('2013-03-01'); $ date-> modifier ('- 1 mois'); $ previousMonth = $ date-> format ('Y-m'); // $ previousMonth est maintenant 2013-02. Créez une requête pour récupérer les produits du mois précédent.
Lamy
Les données de l'année bissextile du 29 février seront perdues, nous devons également en tenir compte. La réponse de Thunder Rabbit ci-dessous considère cela, mais cron s'exécute deux fois en février de l'année bissextile
Hari Swaminathan
1
@Hari, la solution préférée serait d'exécuter le premier du mois et de collecter les données du mois précédent. Le 29 février ne serait pas manqué dans ce cas.
paxdiablo
1
Compte tenu des années bissextiles: et si cela ne vous dérange pas de courir deux fois: 55 23 28,29 2 * myjob.sh
radiantRazor
52

Il existe une méthode légèrement plus courte qui peut être utilisée, similaire à l'une de celles ci-dessus. C'est:

[ $(date -d +1day +%d) -eq 1 ] && echo "last day of month"

En outre, l'entrée crontab pourrait être mise à jour pour ne vérifier que le 28 au 31 car il est inutile de l'exécuter les autres jours du mois. Ce qui vous donnerait:

0 23 28-31 * * [ $(date -d +1day +%d) -eq 1 ] && myscript.sh
Indé
la source
Je ne pouvais pas faire fonctionner cela comme une entrée crontab (quelque chose doit être échappé je pense). Cela fonctionnait bien dans un script shell appelé depuis crontab. FYI, l'erreur que j'ai eue était /bin/sh: -c: line 1: unexpected EOF while looking for matching ')'.
Mark Rajcok
13
Cela fonctionne très bien. Dans le fichier crontab, le% doit être échappé. So[ $(date -d +1day +\%d) -eq 1 ] && run_job
ColinM
Belle astuce en effet! Mais la question a été taguée posixet la date POSIX ne prend pas en charge "-d +1day": - \ Une solution plus compliquée (et moche) serait:[ `date +\%d` -eq `cal | xargs echo | awk '{print $NF}'` ] && myscript.sh
ckujau
18

Configurez une tâche cron à exécuter le premier jour du mois. Modifiez ensuite l'horloge du système pour qu'elle soit un jour à l'avance.

Tom Anderson
la source
10
ce qui signifie que l'horloge du système sera toujours erronée. Désolé mais je pense que cela causera plus de problèmes. -1 alors.
Rudy
69
Maintenant je sais ce que ressentait Galilée.
Tom Anderson
7
@iconoclast: j'aimerais penser que c'est plus un koan qu'une blague.
Tom Anderson
14
Je pense que c'est une idée vraiment mauvaise ET brillante. Ne faites pas ça à la maison, les enfants.
Pascal
11
@pascalbetz Oh, les gens peuvent le faire à la maison, mais ils ne devraient vraiment pas le faire au travail.
Tom Anderson
14

Et celui-ci, après Wikipédia?

55 23 L * * /full/path/to/command
Piohen
la source
Eh bien, qu'en est-il? Que: "mauvaises erreurs de jour du mois dans le fichier crontab, installation impossible. Voulez-vous réessayer la même modification?"
webjunkie
12
Juste pour être clair, cette entrée de wikipedia mentionne également que "L" n'est pas standard.
sdupton le
10

Adaptant la solution de paxdiablo, je cours les 28 et 29 février. Les données du 29 écrasent le 28.

# min  hr  date     month          dow
  55   23  31     1,3,5,7,8,10,12   * /path/monthly_copy_data.sh
  55   23  30     4,6,9,11          * /path/monthly_copy_data.sh
  55   23  28,29  2                 * /path/monthly_copy_data.sh
Lapin tonnerre
la source
4
À moins, bien sûr, que le travail n'ait un aspect destructeur tel que la suppression de toutes les données au fur et à mesure qu'elles sont traitées :-)
paxdiablo
C'est en fait une option indolore (la moins technique), et vous pourriez ne pas vous soucier du fait que trois fois en 4 ans, vous obtiendrez le cronjob trop tôt si vous omettez simplement le ,29.
Matt
@Matt: Euh, tu ne veux pas dire qu'il fonctionnera un jour trop tôt une fois tous les quatre ans si votre entrée crontab le dit 55   23   28    2?
G-Man dit `` Réintégrer Monica '' le
@ G-Man Oui, vous avez raison, et en plus de cela, vous le faites exécuter une deuxième fois le 29.
Matt
8

Vous pouvez configurer une tâche cron à exécuter tous les jours du mois et lui faire exécuter un script shell comme celui-ci. Ce script détermine si le numéro du jour de demain est inférieur à celui d'aujourd'hui (c'est-à-dire si demain est un nouveau mois), puis fait ce que vous voulez.

TODAY=`date +%d`
TOMORROW=`date +%d -d "1 day"`

# See if tomorrow's day is less than today's
if [ $TOMORROW -lt $TODAY ]; then
echo "This is the last day of the month"
# Do stuff...
fi
thomson_matt
la source
Je ne suis pas sûr de ce que vous gagnez en vérifiant si c'est moins qu'aujourd'hui - vous devriez simplement pouvoir vérifier si c'est le premier. Sauf si le premier jour du mois peut être le 2 ou 3 :-)
paxdiablo
7

Pour une méthode plus sûre dans une crontab basée sur la solution @Indie (utiliser le chemin absolu vers date+ $()ne fonctionne pas sur tous les systèmes crontab):

0 23 28-31 * * [ `/bin/date -d +1day +\%d` -eq 1 ] && myscript.sh
zigarn
la source
5
#########################################################
# Memory Aid 
# environment    HOME=$HOME SHELL=$SHELL LOGNAME=$LOGNAME PATH=$PATH
#########################################################
#
# string         meaning
# ------         -------
# @reboot        Run once, at startup.
# @yearly        Run once a year, "0 0 1 1 *".
# @annually      (same as @yearly)
# @monthly       Run once a month, "0 0 1 * *".
# @weekly        Run once a week, "0 0 * * 0".
# @daily         Run once a day, "0 0 * * *".
# @midnight      (same as @daily)
# @hourly        Run once an hour, "0 * * * *".
#mm     hh      Mday    Mon     Dow     CMD # minute, hour, month-day month DayofW CMD
#........................................Minute of the hour
#|      .................................Hour in the day (0..23)
#|      |       .........................Day of month, 1..31 (mon,tue,wed)
#|      |       |       .................Month (1.12) Jan, Feb.. Dec
#|      |       |       |        ........day of the week 0-6  7==0
#|      |       |       |        |      |command to be executed
#V      V       V       V        V      V
*       *       28-31   *       *       [ `date -d +'1 day' +\%d` -eq 1 ] && echo "Tomorrow is the first today now is  `date`" >> ~/message
1       0       1       *       *       rm -f ~/message
*       *       28-31   *       *       [ `date -d +'1 day' +\%d` -eq 1 ] && echo "HOME=$HOME LOGNAME=$LOGNAME SHELL = $SHELL PATH=$PATH" 
Leslie Satenstein
la source
5

Pour l'implémentation cron d'AWS Cloudwatch (planification de Lambdas, etc.), cela fonctionne:

55 23 L * ? *

Diffusion à 23 h 55 le dernier jour de chaque mois.

Dan Herman
la source
4
00 23 * * * [[ $(date +'%d') -eq $(cal | awk '!/^$/{ print $NF }' | tail -1) ]] && job

Consultez une question connexe sur le forum unix.com.

débarrasser
la source
3

Vous pouvez simplement connecter toutes les réponses en une seule ligne cron et utiliser uniquement la datecommande.

Vérifiez simplement la différence entre le jour du mois qui est aujourd'hui et le sera demain:

0 23 * * * root [ $(expr $(date +\%d -d '1 days') - $(date +\%d)  ) -le 0 ]  && echo true

Si ces différences sont inférieures à 0, cela signifie que nous changeons le mois et qu'il y a le dernier jour du mois.

Lukasz Stelmach
la source
3
55 23 28-31 * * echo "[ $(date -d +1day +%d) -eq 1 ] && my.sh" | /bin/bash 
Donald Duck
la source
1
faire écho à la commande et la rediriger vers bash est la meilleure façon de travailler avec cron. Puisque cron utilise sh et non bash. Vérifiez où votre bash utilise 'which bash'. Sur FreeBSD, il s'agit de / usr / local / bin / bash, sous Linux / bin / bash.
Donald Duck
2

Et ça?

modifier l' .bashprofileajout de l'utilisateur :

export LAST_DAY_OF_MONTH=$(cal | awk '!/^$/{ print $NF }' | tail -1)

Ajoutez ensuite cette entrée à crontab:

mm hh * * 1-7 [[ $(date +'%d') -eq $LAST_DAY_OF_MONTH ]] && /absolutepath/myscript.sh
Raul Baron
la source
0

Le dernier jour du mois peut être du 28 au 31 selon le mois (février, mars, etc.). Cependant, dans l'un ou l'autre de ces cas, le jour suivant est toujours le 1er du mois suivant. Nous pouvons donc l'utiliser pour nous assurer d'exécuter une tâche toujours le dernier jour du mois en utilisant le code ci-dessous:

0 8 28-31 * * [ "$(date +%d -d tomorrow)" = "01" ] && /your/script.sh
Rakesh Chintha
la source