Задание CRON для запуска в последний день месяца

95

Мне нужно создать задание CRON, которое будет выполняться в последний день каждого месяца. Я создам его с помощью cPanel.

Любая помощь приветствуется. Спасибо

Утку Далмаз
источник

Ответы:

183

Возможно, самый простой способ - просто выполнить три отдельных задания:

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

Это будет проходить 28 февраля, даже в високосные годы, поэтому, если это проблема, вам нужно найти другой способ.


Однако, как правило, гораздо проще и правильно запускать задание как можно скорее в первый день каждого месяца, например:

0 0 1 * * myjob.sh

и измените сценарий для обработки данных за предыдущий месяц.

Это устраняет любые проблемы, с которыми вы можете столкнуться при определении последнего дня месяца, а также гарантирует, что все данные за этот месяц будут доступны, если вы обрабатываете данные. Если вы бегаете без пяти минут до полуночи в последний день месяца, вы можете пропустить все, что происходит с этого времени до полуночи.

В любом случае это обычный способ сделать это для большинства работ в конце месяца.


Если ты все еще действительно хотите запустить его в последний день месяца, один из вариантов - просто определить, будет ли завтра первым (либо как часть вашего скрипта, либо в самом crontab).

Итак, что-то вроде:

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

должно быть хорошим началом, если у вас есть относительно умная dateпрограмма.

Если ваша dateпрограмма недостаточно продвинута, чтобы давать вам относительные даты, вы можете просто составить очень простую программу, которая покажет вам завтрашний день месяца (вам не нужна полная мощность date), например:

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

а затем используйте (при условии, что вы назвали его tomdom«завтрашним днем ​​месяца»):

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

Хотя вы можете рассмотреть возможность добавления проверки ошибок, поскольку оба time()варианта mktime()могут вернуться, -1если что-то пойдет не так. В приведенном выше коде для простоты это не учитывается.

Paxdiablo
источник
7
да, может быть проще бегать каждый первый день вместо последнего :)
Утку Далмаз
1
Действительно, 1-й день месяца. Вот как будет выглядеть код в PHP $ date = new DateTime ('2013-03-01'); $ date-> modify ('- 1 месяц'); $ previousMonth = $ date-> формат ('Y-m'); // $ previousMonth теперь 2013-02. Создайте запрос для получения товаров за предыдущий месяц.
Lamy
Данные за високосный год 29 февраля будут потеряны, это тоже нужно учитывать. Ответ Громового Кролика ниже учитывает это, но cron запускается дважды в феврале високосного года
Хари Сваминатан
1
@Hari, предпочтительным решением будет запуск первого числа месяца и сбор данных за предыдущий месяц. В таком случае нельзя будет пропустить 29 февраля.
paxdiablo
1
Учитывая високосные годы: и если не возражаете, он будет повторяться дважды: 55 23 28,29 2 * myjob.sh
radiantRazor
52

Есть более короткий метод, который можно использовать, аналогичный одному из описанных выше. То есть:

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

Кроме того, запись crontab может быть обновлена ​​для проверки только с 28 по 31 число, так как запускать ее в другие дни месяца бессмысленно. Что даст вам:

0 23 28-31 * * [ $(date -d +1day +%d) -eq 1 ] && myscript.sh
Инди
источник
Я не мог заставить это работать как запись crontab (я думаю, что-то нужно избежать). Однако он отлично работал в сценарии оболочки, вызываемом из crontab. FYI, я получил ошибку /bin/sh: -c: line 1: unexpected EOF while looking for matching ')'.
Марк Райкок
13
Это прекрасно работает. В файле crontab символ% должен быть экранирован. Итак[ $(date -d +1day +\%d) -eq 1 ] && run_job
ColinM 02
Действительно хороший трюк! Но вопрос был помечен, posixа дата POSIX не поддерживает "-d +1day": - \ Более сложным (и уродливым) решением было бы:[ `date +\%d` -eq `cal | xargs echo | awk '{print $NF}'` ] && myscript.sh
ckujau
15

Настройте задание cron для запуска в первый день месяца. Затем измените системные часы на один день вперед.

Том Андерсон
источник
11
что означает, что системные часы будут всегда ошибаться. Извините, но я думаю, что это вызовет дополнительные проблемы. -1 тогда.
Rudy
70
Теперь я знаю, что чувствовал Галилей.
Tom Anderson
7
@iconoclast: Я бы хотел думать об этом как о коане, а не как о шутке.
Том Андерсон
14
Я думаю, что это действительно плохая И блестящая идея. Не делайте этого дома, детки.
Паскаль
11
@pascalbetz О, люди могут делать это дома, но им действительно не следует делать это на работе.
Том Андерсон
14

Что насчет этого, после Википедии?

55 23 L * * /full/path/to/command
Piohen
источник
Ну что с этим? Это: «плохие ошибки дня месяца в файле crontab, не удается установить. Хотите повторить то же изменение?»
webjunkie
12
Чтобы прояснить, в этой статье в Википедии также упоминается, что буква L нестандартна.
sdupton
10

Адаптируя решение paxdiablo, я бегу 28 и 29 февраля. Данные от 29 числа перезаписывают данные от 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
Громовой кролик
источник
4
Если, конечно, задание не имеет деструктивного аспекта, такого как очистка всех данных по мере их обработки :-)
paxdiablo
На самом деле это безболезненный вариант (наименее технический), и вам может быть все равно, что трижды за 4 года вы получите задание cron слишком рано, если просто опустите ,29.
Мэтт
@Matt: Умм, разве вы не имеете в виду, что он будет запускаться на день раньше, чем раз в четыре года, если в вашей записи crontab написано 55   23   28    2?
G-Man говорит: «Reinstate Monica»
@ G-Man Да, вы правы, и, вдобавок ко всему, у вас есть второй запуск 29-го числа.
Мэтт
8

Вы можете настроить задание cron для запуска каждый день месяца и запустить сценарий оболочки, как показано ниже. Этот сценарий определяет, будет ли номер завтрашнего дня меньше сегодняшнего (т.е. если завтра новый месяц), а затем делает все, что вы хотите.

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
источник
Я не уверен, что вы получите, проверив, меньше ли оно, чем сегодня - вы должны просто проверить, первое ли оно. Если только первый день месяца не может быть 2-м или 3-м :-)
paxdiablo
7

Для более безопасного метода в crontab на основе решения @Indie (использование абсолютного пути к date+ $()не работает во всех системах crontab):

0 23 28-31 * * [ `/bin/date -d +1day +\%d` -eq 1 ] && myscript.sh
зигарн
источник
6

Некоторые реализации cron поддерживают флаг «L» для обозначения последнего дня месяца.

Если вам повезло использовать одну из этих реализаций, это очень просто:

0 55 23 L * ?

Это будет происходить в 23:55 в последний день каждого месяца.

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

Ной Хелдман
источник
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" 
Лесли Сатенштейн
источник
5

Для реализации cron AWS Cloudwatch (планирование лямбда-выражений и т. Д.) Это работает:

55 23 L * ? *

Запуск в 23:55 в последний день каждого месяца.

Дэн Герман
источник
3

Вы можете просто связать все ответы в одной строке cron и использовать только dateкоманду.

Просто проверьте разницу между днем ​​месяца, который сегодня и будет завтра:

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

Если эта разница меньше 0, это означает, что мы меняем месяц и есть последний день месяца.

Лукаш Стельмах
источник
3
55 23 28-31 * * echo "[ $(date -d +1day +%d) -eq 1 ] && my.sh" | /bin/bash 
Дональд Дак
источник
1
отображение команды и передача ее в bash - лучший способ при работе с cron. Поскольку cron использует sh, а не bash. Проверьте, где ваш bash использует «which bash». Во FreeBSD это / usr / local / bin / bash, в Linux / bin / bash.
Дональд Дак
2

Как насчет этого?

редактировать .bashprofileдобавление пользователя :

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

Затем добавьте эту запись в crontab:

mm hh * * 1-7 [[ $(date +'%d') -eq $LAST_DAY_OF_MONTH ]] && /absolutepath/myscript.sh
Рауль Барон
источник
0

Последний день месяца может быть 28-31 в зависимости от того, какой сейчас месяц (февраль, март и т. Д.). Однако в любом из этих случаев следующий день всегда является 1-м числом следующего месяца. Таким образом, мы можем использовать это, чтобы всегда запускать какое-то задание в последний день месяца, используя приведенный ниже код:

0 8 28-31 * * [ "$(date +%d -d tomorrow)" = "01" ] && /your/script.sh
Ракеш Чинта
источник