Zadanie CRON do uruchomienia ostatniego dnia miesiąca

95

Muszę utworzyć zadanie CRON, które będzie uruchamiane ostatniego dnia każdego miesiąca. Stworzę go za pomocą cPanel.

Każda pomoc jest mile widziana. Dzięki

Utku Dalmaz
źródło

Odpowiedzi:

183

Prawdopodobnie najłatwiej jest po prostu wykonać trzy oddzielne zadania:

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

Będzie to jednak działać 28 lutego, nawet w latach przestępnych, więc jeśli to jest problem, musisz znaleźć inny sposób.


Jednak zazwyczaj znacznie łatwiejsze i poprawne jest uruchamianie zadania tak szybko, jak to możliwe pierwszego dnia każdego miesiąca, z czymś takim:

0 0 1 * * myjob.sh

i zmodyfikuj skrypt, aby przetwarzał dane z poprzedniego miesiąca.

Eliminuje to wszelkie problemy, które możesz napotkać przy ustalaniu, który dzień jest ostatnim dniem miesiąca, a także zapewnia, że ​​wszystkie dane z tego miesiąca są dostępne, zakładając, że przetwarzasz dane. Bieganie od pięciu minut do północy ostatniego dnia miesiąca może spowodować, że przegapisz wszystko, co wydarzy się między tą chwilą a północą.

W każdym razie jest to typowy sposób wykonywania większości prac na koniec miesiąca.


Jeśli nadal naprawdę chcesz go uruchomić ostatniego dnia miesiąca, jedną z opcji jest po prostu wykrycie, czy jutro jest pierwsze (w ramach skryptu lub w samej tabeli crontab).

A więc coś takiego:

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

powinien być dobrym początkiem, zakładając, że masz stosunkowo inteligentny dateprogram.

Jeśli Twój dateprogram nie jest wystarczająco zaawansowany, aby podać daty względne, możesz po prostu ułożyć bardzo prosty program, aby podać jutrzejszy dzień miesiąca (nie potrzebujesz pełnej mocy date), na przykład:

#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;
}

a następnie użyj (zakładając, że nazwałeś to tomdom„jutrzejszym dniem miesiąca”):

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

Chociaż możesz rozważyć dodanie funkcji sprawdzania błędów, ponieważ oba time()i mktime()mogą zwrócić, -1jeśli coś pójdzie nie tak. Powyższy kod ze względu na prostotę nie bierze tego pod uwagę.

paxdiablo
źródło
7
tak, może być łatwiej biegać każdego pierwszego dnia zamiast ostatniego :)
Utku Dalmaz
1
Rzeczywiście pierwszy dzień miesiąca. Oto jak będzie wyglądał kod w PHP $ date = new DateTime ('2013-03-01'); $ date-> modyfikuj ('- 1 miesiąc'); $ previousMonth = $ date-> format ('Y-m'); // $ previousMonth to teraz 2013-02. Utwórz zapytanie, aby pobrać produkty z poprzedniego miesiąca.
Lamy
Dane za rok przestępny z 29 lutego zostaną utracone, musimy to również wziąć pod uwagę. Odpowiedź Thunder Rabbit poniżej rozważ to, ale cron działa dwa razy w
Hari Swaminathan,
1
@Hari, preferowanym rozwiązaniem byłoby uruchomienie pierwszego dnia miesiąca i zebranie danych z poprzedniego miesiąca. W takim przypadku nie można przegapić 29 lutego.
paxdiablo
1
Biorąc pod uwagę lata przestępne: i jeśli nie przeszkadza ci to dwukrotne uruchomienie: 55 23 28,29 2 * myjob.sh
radiantRazor
52

Istnieje nieco krótsza metoda, której można użyć podobnie do jednej z powyższych. To jest:

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

Ponadto wpis crontab może zostać zaktualizowany, aby sprawdzać tylko od 28 do 31, ponieważ nie ma sensu uruchamiać go w inne dni miesiąca. Co dałoby ci:

0 23 28-31 * * [ $(date -d +1day +%d) -eq 1 ] && myscript.sh
Indie
źródło
Nie mogłem zmusić tego do działania jako wpis w pliku crontab (myślę, że trzeba coś uciec). Jednak działał dobrze w skrypcie powłoki wywołanym z crontab. FYI, otrzymałem błąd /bin/sh: -c: line 1: unexpected EOF while looking for matching ')'.
Mark Rajcok
13
To działa świetnie. W pliku crontab należy zmienić znaczenie znaku%. A więc[ $(date -d +1day +\%d) -eq 1 ] && run_job
ColinM,
Naprawdę niezła sztuczka! Ale pytanie zostało otagowane, posixa data POSIX nie obsługuje "-d +1day": - \ Bardziej skomplikowanym (i brzydkim) rozwiązaniem byłoby:[ `date +\%d` -eq `cal | xargs echo | awk '{print $NF}'` ] && myscript.sh
ckujau
15

Skonfiguruj zadanie cron, aby było uruchamiane pierwszego dnia miesiąca. Następnie zmień zegar systemowy na jeden dzień do przodu.

Tom Anderson
źródło
11
co oznacza, że ​​zegar systemowy będzie cały czas nieprawidłowy. Przepraszam, ale myślę, że spowoduje to więcej problemów. -1 wtedy.
Rudy
70
Teraz wiem, jak czuł się Galileo.
Tom Anderson
7
@iconoclast: Chciałbym myśleć o tym bardziej jak o koanie niż żartie.
Tom Anderson,
14
Myślę, że to naprawdę zły I genialny pomysł. Nie róbcie tego w domu, dzieciaki.
Pascal,
11
@pascalbetz Och, ludzie mogą to robić w domu, ale naprawdę nie powinni tego robić w pracy.
Tom Anderson,
14

A co z tym, po Wikipedii?

55 23 L * * /full/path/to/command
Piohen
źródło
A co z tym? To: „błędne błędy dnia miesiąca w pliku crontab, nie można zainstalować. Czy chcesz ponowić tę samą edycję?”
webjunkie
12
Dla jasności, wpis na Wikipedii wspomina również, że „L” jest niestandardowy.
sdupton
10

Adaptując rozwiązanie paxdiablo, biegam 28 i 29 lutego. Dane z 29 stycznia zastępują 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
Thunder Rabbit
źródło
4
Chyba że, oczywiście, praca ma jakiś destrukcyjny aspekt, taki jak usuwanie wszystkich danych w trakcie ich przetwarzania :-)
paxdiablo
W rzeczywistości jest to bezbolesna opcja (najmniej techniczna) i możesz nie przejmować się tym, że trzykrotnie w ciągu 4 lat dostaniesz cronjob zbyt wcześnie, jeśli po prostu pominiesz ,29.
Matt
@Matt: Umm, czy nie masz na myśli, że będzie działał o jeden dzień za wcześnie raz na cztery lata, jeśli twój wpis w crontab mówi 55   23   28    2?
G-Man mówi „Przywróć Monikę”
@ G-Man Tak, masz rację, a do tego 29. dnia po raz drugi.
Matt
8

Możesz skonfigurować zadanie crona do uruchamiania każdego dnia miesiąca i uruchomić skrypt powłoki, taki jak poniżej. Ten skrypt sprawdza, czy numer jutrzejszego dnia jest mniejszy niż dzisiejszy (tj. Czy jutro jest nowy miesiąc), a następnie robi, co chcesz.

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
źródło
Nie jestem pewien, co zyskujesz, sprawdzając, czy to mniej niż dzisiaj - powinieneś po prostu być w stanie sprawdzić, czy to pierwsza. Chyba że pierwszy dzień miesiąca może być 2 lub 3 :-)
paxdiablo
7

Dla bezpieczniejszej metody w tabeli crontab opartej na rozwiązaniu @Indie (użyj bezwzględnej ścieżki do date+ $()nie działa na wszystkich systemach crontab):

0 23 28-31 * * [ `/bin/date -d +1day +\%d` -eq 1 ] && myscript.sh
zigarn
źródło
6

Niektóre implementacje crona obsługują flagę „L”, która reprezentuje ostatni dzień miesiąca.

Jeśli masz szczęście, że korzystasz z jednej z tych implementacji, jest to tak proste, jak:

0 55 23 L * ?

Będzie to działać ostatniego dnia każdego miesiąca o 23:55.

http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger

Noah Heldman
źródło
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
źródło
5

W przypadku implementacji cron AWS Cloudwatch (planowanie lambdas itp.) Działa to:

55 23 L * ? *

Wyświetlanie o godzinie 23:55 ostatniego dnia każdego miesiąca.

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

Sprawdź powiązane pytanie na forum unix.com.

pozbyć się
źródło
3

Możesz po prostu połączyć wszystkie odpowiedzi w jednej linii crona i użyć tylko datepolecenia.

Wystarczy sprawdzić różnicę między dniem miesiąca, który jest dzisiaj i jutro:

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

Jeśli ta różnica jest poniżej 0, oznacza to, że zmieniamy miesiąc i jest ostatni dzień miesiąca.

Łukasz Stelmach
źródło
3
55 23 28-31 * * echo "[ $(date -d +1day +%d) -eq 1 ] && my.sh" | /bin/bash 
Kaczor Donald
źródło
1
powtórzenie polecenia i przekazanie go do bash jest najlepszym sposobem podczas pracy z cronem. Ponieważ cron używa sh, a nie bash. Sprawdź, gdzie twój bash używa „który bash”. We FreeBSD jest to / usr / local / bin / bash, w systemie Linux / bin / bash.
Kaczor Donald,
2

A co z tym?

edytuj .bashprofiledodawanie użytkownika :

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

Następnie dodaj ten wpis do crontab:

mm hh * * 1-7 [[ $(date +'%d') -eq $LAST_DAY_OF_MONTH ]] && /absolutepath/myscript.sh
Raul Baron
źródło
0

Ostatni dzień miesiąca może przypadać na 28-31, w zależności od tego, jaki jest miesiąc (luty, marzec itp.). Jednak w każdym z tych przypadków następny dzień jest zawsze pierwszym dniem następnego miesiąca. Możemy więc użyć tego, aby upewnić się, że wykonujemy zadanie zawsze ostatniego dnia miesiąca, używając poniższego kodu:

0 8 28-31 * * [ "$(date +%d -d tomorrow)" = "01" ] && /your/script.sh
Rakesh Chintha
źródło