Запускать работу cron в первый понедельник каждого месяца?
Я хотел бы запустить работу из cron в 8.30 в первый понедельник каждого месяца. На странице cron Википедии написано
Хотя обычно задание выполняется, когда все поля спецификации времени / даты соответствуют текущему времени и дате, есть одно исключение: если и "день месяца", и "день недели" ограничены (не "*"), то либо поле "день месяца" (3) или поле "день недели" (5) должны соответствовать текущему дню.
(мой акцент)
Означает ли это, что я не могу сделать первый понедельник месяца, я могу сделать только первый (или любой другой) день месяца? Я не могу придумать способ обойти это.
19 ответов
Вы можете поместить условие в фактическую команду crontab:
[ "$(date '+%a')" = "Mon" ] && echo "It's Monday"
Теперь, если это условие выполняется в один из первых семи дней месяца, у вас есть первый понедельник. Обратите внимание, что в crontab процентный синтаксис необходимо экранировать, хотя:
0 12 1-7 * * [ "$(date '+\%a')" = "Mon" ] && echo "It's Monday"
Заменить echo
команда с фактической командой, которую вы хотите запустить. Я нашел аналогичный подход тоже.
У меня есть компьютер с языком на испанском языке, поэтому этот подход не работает для меня, потому что понедельник меняется на лун
Другие языки тоже потерпят неудачу, поэтому я немного изменил принятый ответ, который устраняет языковой барьер:
0 9 1-7 * * [ "$(date '+\%u')" = "1" ] && echo "¡Es lunes!"
Мне легче, когда нет необходимости обрабатывать номера дней.
Запустите первый понедельник месяца:
0 2 * * 1 [ `date '+\%m'` == `date '+\%m' -d "1 week ago"` ] || /path/to/command
т.е. если месяц 1 неделя назад не совпадает с текущим месяцем, то мы находимся в 1-й день 1 (= понедельник) месяца.
Точно так же на третью пятницу
0 2 * * 6 [ `date '+\%m'` == `date '+\%m' -d "3 weeks ago"` ] || /path/to/command
т.е. если месяц 3 недели назад отличается от текущего месяца, то мы находимся на 3-й день 6 (= пятница) месяца
Поскольку я интерпретирую свои выражения cron с помощью php и js, я не могу использовать bash. Наконец я обнаружил, что это на самом деле возможно только с помощью cron:
0 30 8 * 1/1 MON#1
Надеюсь, это поможет кому-то еще. Несмотря ни на что, я желаю вам прекрасного дня.:-)
Я запланировал работу на 4-й понедельник каждого месяца в 16:00 следующим образом:
0 16 22-28 * Mon [ "$(date '+\%a')" == "Mon" ] && touch /home/me/FourthMonOfMonth.txt
Есть хитрый способ сделать это с помощью классического cron (Vixie, Debian):
30 8 */100,1-7 * MON
Поле дня месяца начинается со звездочки (*
), поэтому cron считает его «неограниченным» и использует логику «И» между полями дня месяца и дня недели.
*/100
означает «каждые 100 дней, начиная с даты 1». Поскольку нет месяцев с числом дней более 100,*/100,1-7
фактически означает «в даты с 1 по 7».
Вот моя статья с более подробной информацией: Запланируйте Cronjob на первый понедельник каждого месяца, Funky Way.
Я рекомендую использовать
"$(/bin/date '+%\w')" = "1"
вместо
"$(date '+\%a')" = "Mon"
чтобы избежать языковой проблемы.
Поле дня месяца здесь — */100,1-7, что означает «каждые 100 дней, начиная с даты 1, а также в даты 1-7». Поскольку месяцев со 100+ днями не бывает, то это опять же трюк, сказать «в даты с 1 по 7», но с ведущей звездой. Из-за звездочки cron выполнит команду в дни с 1 по 7, которые также приходятся на понедельник.
0 22 */100,1-7 * 2
В примере cron Guru ниже вы можете увидеть результат и дату следующего запуска.
Вы можете попробовать запустить cronjob в течение первых семи дней месяца и разрешить его выполнение только в понедельник.
30 8 * * 1 [`date +\%d` -le 07] && <job>
Выше должно работать на вас.
Этот ответ расширяет ответ @ChiragPansheriya на тот же вопрос .
вр; доктор
Проведите быструю проверку реализации вашего cron, вставив это в свой crontab:
# For 1st Monday (or Tuesday, etc) of month, use */32,1-7 for days of month
# For 2nd Monday (or Tuesday, etc) of month, use */32,8-14 for days of month
# For 3rd Monday (or Tuesday, etc) of month, use */32,15-21 for days of month
# For 4th Monday (or Tuesday, etc) of month, use */32,22-28 for days of month
# For 5th Monday (or Tuesday, etc) of month, use */32,29-31 for days of month
# Explanation: https://superuser.com/a/1813556
* * */32,1-7 * 0 echo "First Sunday" >>cron_out.txt
* * */32,8-14 * 0 echo "Second Sunday" >>cron_out.txt
* * */32,15-21 * 0 echo "Third Sunday" >>cron_out.txt
* * */32,22-28 * 0 echo "Fourth Sunday" >>cron_out.txt
* * */32,29-31 * 0 echo "Fifth Sunday" >>cron_out.txt
* * */32,1-7 * 1 echo "First Monday" >>cron_out.txt
* * */32,8-14 * 1 echo "Second Monday" >>cron_out.txt
* * */32,15-21 * 1 echo "Third Monday" >>cron_out.txt
* * */32,22-28 * 1 echo "Fourth Monday" >>cron_out.txt
* * */32,29-31 * 1 echo "Fifth Monday" >>cron_out.txt
* * */32,1-7 * 2 echo "First Tuesday" >>cron_out.txt
* * */32,8-14 * 2 echo "Second Tuesday" >>cron_out.txt
* * */32,15-21 * 2 echo "Third Tuesday" >>cron_out.txt
* * */32,22-28 * 2 echo "Fourth Tuesday" >>cron_out.txt
* * */32,29-31 * 2 echo "Fifth Tuesday" >>cron_out.txt
* * */32,1-7 * 3 echo "First Wednesday" >>cron_out.txt
* * */32,8-14 * 3 echo "Second Wednesday" >>cron_out.txt
* * */32,15-21 * 3 echo "Third Wednesday" >>cron_out.txt
* * */32,22-28 * 3 echo "Fourth Wednesday" >>cron_out.txt
* * */32,29-31 * 3 echo "Fifth Wednesday" >>cron_out.txt
* * */32,1-7 * 4 echo "First Thursday" >>cron_out.txt
* * */32,8-14 * 4 echo "Second Thursday" >>cron_out.txt
* * */32,15-21 * 4 echo "Third Thursday" >>cron_out.txt
* * */32,22-28 * 4 echo "Fourth Thursday" >>cron_out.txt
* * */32,29-31 * 4 echo "Fifth Thursday" >>cron_out.txt
* * */32,1-7 * 5 echo "First Friday" >>cron_out.txt
* * */32,8-14 * 5 echo "Second Friday" >>cron_out.txt
* * */32,15-21 * 5 echo "Third Friday" >>cron_out.txt
* * */32,22-28 * 5 echo "Fourth Friday" >>cron_out.txt
* * */32,29-31 * 5 echo "Fifth Friday" >>cron_out.txt
* * */32,1-7 * 6 echo "First Saturday" >>cron_out.txt
* * */32,8-14 * 6 echo "Second Saturday" >>cron_out.txt
* * */32,15-21 * 6 echo "Third Saturday" >>cron_out.txt
* * */32,22-28 * 6 echo "Fourth Saturday" >>cron_out.txt
* * */32,29-31 * 6 echo "Fifth Saturday" >>cron_out.txt
Объяснение
Можно задаться вопросом:
Элемент в поле дней месяца всегда имеет значение false (ни один месяц не содержит более 31 дня), так зачем его включать?
Оказывается, если первым символом полей дней месяца или дней недели является звездочка (), cron переключается с объединения полей дней месяца и дней недели на их пересечение . */32
Элемент существует просто для того, чтобы вызвать это изменение в поведении.
На это намекает справочная страница cronie née vixie-cron:
Примечание. День выполнения команды можно указать в следующих двух полях — «день месяца» и «день недели». Если оба поля ограничены (т. е. не содержат символ «*»), команда будет запущена, когда любое поле соответствует текущему времени. Например, «30 4 1,15 * 5» приведет к запуску команды в 4:30 утра 1-го и 15-го числа каждого месяца, а также каждую пятницу.
— https://www.mankier.com/5/crontab
И наоборот: если день месяца или день недели содержат этот символ, команда будет запущена только тогда, когда оба поля соответствуют текущему времени.
Вы можете увидеть, как присутствие ведущего символа в поле дней месяца меняет интерпретацию crontab.guru :
Изучение исходного кода
Фраза, используемая на странице руководства, «содержать символ», двусмысленна. Означает ли это, состоять исключительно из персонажа? Или это означает, что символ должен находиться в любой точке поля? Просматривая исходный код vixie cron ( entry.c), мы видим, что флаги и устанавливаются, если первым символом их соответствующих полей является звездочка ().
/* DOM (days of month)
*/
if (ch == '*')
e->flags |= DOM_STAR; ①
ch = get_list(e->dom, FIRST_DOM, LAST_DOM,
PPC_NULL, ch, file);
if (ch == EOF) {
ecode = e_dom;
goto eof;
}
[...]
/* DOW (days of week)
*/
if (ch == '*')
e->flags |= DOW_STAR; ②
ch = get_list(e->dow, FIRST_DOW, LAST_DOW,
DowNames, ch, file);
if (ch == EOF) {
ecode = e_dow;
goto eof;
}
① Флаг устанавливается, если первый символ поля дней месяца является символом.
② Флаг устанавливается, если первый символ поля дней недели является символом.
Позже cron.c
, если установлен любой из этих двух флагов, cron переключает логический оператор между полями дней месяца и дней недели с ИЛИ на И:
/* the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
* first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
* on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
* is why we keep 'e->dow_star' and 'e->dom_star'. yes, it's bizarre.
* like many bizarre things, it's the standard.
*/
for (u = db->head; u != NULL; u = u->next) {
for (e = u->crontab; e != NULL; e = e->next) {
Debug(DSCH|DEXT, ("user [%s:%d:%d:...] cmd=\"%s\"\n",
env_get("LOGNAME", e->envp),
e->uid, e->gid, e->cmd))
if (bit_test(e->minute, minute) &&
bit_test(e->hour, hour) &&
bit_test(e->month, month) &&
( ((e->flags & DOM_STAR) || (e->flags & DOW_STAR)) ①
? (bit_test(e->dow,dow) && bit_test(e->dom,dom)) ②
: (bit_test(e->dow,dow) || bit_test(e->dom,dom)))) { ③
if ((doNonWild && !(e->flags & (MIN_STAR|HR_STAR)))
|| (doWild && (e->flags & (MIN_STAR|HR_STAR))))
job_add(e, u);
}
}
}
①DOM_STAR
иDOW_STAR
флаги проверяются
② Логический оператор ИЛИ вызывает объединение полей дней месяца и дней недели
③ Логический оператор И вызывает пересечение полей дней месяца и дней недели
Мы видели здесь, что тест «содержания символа», упомянутый на странице руководства, на самом деле является проверкой того, является ли*
символ — первый символ поля.
Внимание: насколько я могу судить, такое поведение не требуется POSIX. (См. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html). YMMV в зависимости от вашей реализации cron. Я могу подтвердить, что метод, описанный в этом ответе, работает на cronie ( производная vixie-cron ). Это также понимается crontab.guru (пример: 0 8 */32,1-7 * 1
).
Некоторая справочная информация об этом поведении включена в файл FEATURES исходного кода vixie cron:
-- the dom/dow situation is odd. '* * 1,15 * Sun' will run on the
first and fifteenth AND every Sunday; '* * * * Sun' will run *only*
on Sundays; '* * 1,15 * *' will run *only* the 1st and 15th. this
is why we keep 'e->dow_star' and 'e->dom_star'. I didn't think up
this behaviour; it's how cron has always worked but the documentation
hasn't been very clear. I have been told that some AT&T crons do not
act this way and do the more reasonable thing, which is (IMHO) to "or"
the various field-matches together. In that sense this cron may not
be completely similar to some AT&T crons.
Сравнение
Вот сравнение того, как эти два подхода выглядят в crontab:
0 8 1-7 * * [ "$(date '+\%u')" = "1" ] && echo "First Monday"
0 8 */32,1-7 * 1 echo "First Monday"
(В первой строке показан подход принятого ответа ).
Связанные обсуждения
Почему crontab использует OR, если указаны и день месяца, и день недели?
https://stackoverflow.com/a/51342252Являются ли поля crontab «день месяца» и «день недели» взаимоисключающими?
https://unix.stackexchange.com/q/602328
Насколько я знаю, это невозможно при использовании только crontab, однако можно использовать функцию-обертку, чтобы выбрать правильный день из записи contab "первые семь дней месяца"; увидеть это из записи.
Скрипт-обёртка будет
#! /usr/bin/ksh
day=$(date +%d)
if ((day <= 7)) ; then
exec somecommand
fi
exit 1
и вам нужно будет запустить его (при условии, что он называется wrapper.sh и доступен глобально), используя запись crontab
0 0 * * 1 wrapper.sh
На Solaris 10 мне пришлось отформатировать условие следующим образом:
[ `date +\%a` = "Sat" ] && echo "It's Saturday"
Я сделал общее решение для такого рода проблем, оно работает для первого, второго, третьего... последнего дня недели месяца.
Вы можете использовать это так:
30 06 * * Mon run-if-today 1 "Mon" && echo "First Monday"
30 06 * * Thu run-if-today 3 "Thu" && echo "Third Thursday"
30 06 * * Sun run-if-today L "Sun" && echo "Last Sunday"
Скрипт run-if-today проверяет как день недели, так и желаемый диапазон дат недели, если оба совпадают, возвращается 0, в противном случае - 1.
Проверьте код здесь. https://github.com/xr09/cron-last-sunday
Я только что столкнулся с этим и вот что у меня получилось:
0 11 * * 1#1 - Run at 11 am on the first Monday of every month
0 11 * * 1#2 - Run at 11 am on the second Monday of every month
0 11 * * 1#3 - Run at 11 am on the third Monday of every month
Если вместо этого вы хотите бежать во вторник:
0 11 * * 2#1 - Run at 11 am on the first Tuesday of every month
0 11 * * 2#2 - Run at 11 am on the second Tuesday of every month
0 11 * * 2#3 - Run at 11 am on the third Tuesday of every month
crontab 30 8 */27 * 1
В 08:30, [один] каждые 27 дней и в понедельник (см. генератор выражений crontab )
День месяца не указан ( является *), поэтому ни одна логика/исключение не применяется.
Такое использование должно быть наиболее универсальным и позволяет избежать проблем с локалью.
[ `/bin/date +\%u` -eq 1 ]
первый понедельник месяца в 6 утра будет выглядеть так:/etc/crontab
00 6 1-7 * * root [ `/bin/date +\%u` -eq 1 ] && /run/yourjob.sh
Мне нужен был тот же результат, но я хотел использовать чистый cron. Я надеюсь, что это улучшит принятый ответ.
Мне нужно было, чтобы он запускался в первый понедельник каждого месяца в полдень.
Итак, это должно работать: В полдень, в первые 7 дней месяца, в понедельник: 0 12 1-7 * 1.
Я считаю, что это решает проблему более элегантно:
30 8 1-7 * 1 /run/your/job.sh
0 9 1-7 * 1 *
Это будет работать каждый понедельник каждого месяца.