Выражения cron: пять полей и примеры
Выражение cron это пять полей через пробел, которые задают момент запуска задачи: минута, час, день месяца, месяц, день недели. Читая слева направо, 30 2 * * 1 означает в 30-ю минуту второго часа любого дня любого месяца, если этот день понедельник.
Пять полей
Любая стандартная строка Unix cron начинается с одних и тех же пяти полей, за которыми идет команда.
* * * * * command to run
| | | | |
| | | | +-- day of week (0-7, 0 and 7 both mean Sunday, or SUN-SAT)
| | | +---- month (1-12, or JAN-DEC)
| | +------ day of month (1-31)
| +-------- hour (0-23)
+---------- minute (0-59)
В поле стоит одно из четырех: звездочка, означающая любое значение, одно число, список или диапазон. Часы считаются по 24-часовым, без AM и PM, поэтому 6 вечера это 18. Воскресенье это и 0, и 7, потому что исторически системы расходились в этом вопросе, и cron принимает оба варианта.
Некоторые планировщики добавляют поля. Quartz, который используют многие Java-фреймворки, ставит первым поле секунд и добавляет необязательный год в конце, поэтому выражение из шести или семи полей, скопированное из справочника по Quartz, в crontab на Linux не заработает. Прежде чем копировать выражение откуда бы то ни было, посмотрите, для какого диалекта вы пишете.
Специальные символы
*совпадает с любым значением поля.* * * * *запускается каждую минуту.,перечисляет значения.0 8,13,20 * * *запускается в 08:00, 13:00 и 20:00.-задает диапазон.0 9-17 * * *запускается ровно в час с 09:00 по 17:00./задает шаг по диапазону.*/15 * * * *запускается на 0, 15, 30 и 45-й минуте. Шаг можно совместить с диапазоном:0 9-17/2 * * *запускается в 9, 11, 13, 15 и 17.- Имена.
MONпоSUNиJANпоDECработают в последних двух полях и читаются лучше цифр. Внутри шага их использовать нельзя.
Еще четыре символа постоянно попадаются в руководствах и в стандартном cron не существуют. L для последнего дня, W для ближайшего рабочего дня, # для n-го дня недели в месяце и ? вместо звездочки это расширения Quartz. Выражение вида 0 0 L * * crontab на Linux отвергнет. Чтобы запустить что-то в последний день месяца, запускайте ежедневно и пусть решает сама команда:
0 0 * * * [ "$(date -d tomorrow +\%d)" = "01" ] && /usr/local/bin/month-end.sh
Обратите внимание на обратные слэши в этой строке. Знак процента в crontab имеет особый смысл, и это пятая ошибка из списка ниже.
Cron принимает также шесть сокращений вместо всех пяти полей: @yearly (пишется и как @annually), @monthly, @weekly, @daily (он же @midnight), @hourly и @reboot, который срабатывает один раз при старте cron, а не по часам. @reboot поддерживается не везде.
Частые расписания
| Когда нужно запускать | Выражение |
|---|---|
| Каждую минуту | * * * * * |
| Каждые 5 минут | */5 * * * * |
| Каждые 30 минут | 0,30 * * * * |
| Каждый час, ровно в час | 0 * * * * |
| Каждые 2 часа | 0 */2 * * * |
| Каждый день в полночь | 0 0 * * * |
| Каждый день в 02:30 | 30 2 * * * |
| Дважды в день, в 08:00 и 20:00 | 0 8,20 * * * |
| По будням в 09:00 | 0 9 * * MON-FRI |
| Каждый час в рабочие часы по будням | 0 9-17 * * MON-FRI |
| Каждый понедельник в 07:00 | 0 7 * * MON |
| Каждое воскресенье в 03:15 | 15 3 * * SUN |
| Первого числа месяца в 06:00 | 0 6 1 * * |
| Раз в квартал, 1 января, апреля, июля и октября | 0 0 1 1,4,7,10 * |
Ошибки, из-за которых задача уходит не на то время
- Заполнены оба поля дня. На этом спотыкаются все. Когда день месяца и день недели оба заданы не звездочкой, cron запускает задачу, если совпало любое из них, а не оба сразу.
0 0 13 * FRIсработает каждое 13-е число и каждую пятницу, а не в пятницу 13-го. - Шаг, который не делит диапазон нацело.
*/7 * * * *срабатывает на минутах 0, 7, 14, 21, 28, 35, 42, 49 и 56, а затем начинает с 0. Промежуток через границу часа получается четыре минуты, а не семь. Та же ловушка ждет*/40в поле минут и любой шаг, не являющийся делителем 60. - Шаг не в том поле.
*/2 * * * *это каждые две минуты.0 */2 * * *это каждые два часа. Пропущенный ведущий0превращает ежечасную задачу в задачу, которая работает в 30 раз чаще. - Расчет на привычное окружение. Cron дает задаче минимальное окружение, обычно чуть больше, чем
PATH=/usr/bin:/binиSHELL=/bin/sh. Скрипт, который работает в вашей оболочке, в cron падает, потому что вызывает что-то из/usr/local/bin. Пишите абсолютные пути и в команде, и внутри нее. - Неэкранированный знак процента. В crontab
%превращается в перевод строки, и все, что идет после первого такого знака, подается команде на стандартный ввод.date +%Y-%m-%dвнутри строки crontab приходится писать какdate +\%Y-\%m-\%d. - Игнорирование вывода. Все, что задача печатает, уходит письмом владельцу crontab, а на машине без настроенной почты просто выбрасывается. Отправьте вывод туда, куда вы посмотрите:
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1 - Наложение запусков. Cron начинает следующий запуск по расписанию, не глядя, закончился ли предыдущий. Оберните долгие задачи в блокировку:
*/5 * * * * flock -n /tmp/sync.lock /usr/local/bin/sync.sh - Забытый перевод часов. Cron работает по локальному времени машины. В дни перевода часов час около 02:00 либо не существует, либо случается дважды, поэтому задача, назначенная внутри него, будет пропущена или выполнена два раза, в зависимости от реализации cron. Держите серверы в UTC или назначайте запуски вне этого окна.
- Набранное
crontab -r. Команда стирает весь crontab без подтверждения, а на клавиатуре стоит в одной кнопке отcrontab -e. Держите crontab в системе контроля версий и ставьте его из файла.
Проверка снаружи
Выражение, которое разбирается, и задача, которая отработала, это не одно и то же. Проверьте оба факта: сначала на машине, потом снаружи нее.
На машине убедитесь, что установлено, что показывают часы и запускал ли cron задачу:
crontab -l
timedatectl
grep CRON /var/log/syslog | tail -20
journalctl -u cron -u crond --since "2 hours ago"
Строка в журнале доказывает, что cron запустил команду. Про успех самой команды она не говорит ничего, и именно поэтому важно перенаправление в файл журнала из списка ошибок выше.
Затем проверьте результат так, как его увидит пользователь, снаружи сервера. Почти все задачи по расписанию существуют ради наблюдаемого изменения: лента, в которой должна стоять сегодняшняя дата, карта сайта, которую надо было перезаписать, выгрузка, которая должна была появиться. Запросите ее и прочитайте заголовки:
curl -sI https://example.com/exports/daily.json
curl -s https://example.com/health/last-run
Заголовок Last-Modified покажет, свежий ли файл, и сделает это без доступа к оболочке. Тот же запрос из нескольких локаций через проверку HTTP отделяет невыполненную задачу от страницы, которая закэширована на краевом сервере в одном регионе и устарела только там. Одиночный curl на самом сервере такую разницу не покажет.
Как узнать, что задача по расписанию перестала работать
Остановившаяся задача cron не производит ничего, что можно было бы заметить. Нет ни страницы с ошибкой, ни неудачного запроса, поэтому оповещение не сработает, если только кто-то не следит за самим отсутствием. Следить надо не за задачей, а за ее следом: файлом состояния, который она перезаписывает, отметкой времени, которую публикует, endpoint, который сообщает время последнего успешного запуска. Проверка такого адреса по расписанию превращает тихий отказ в уведомление, а HostTracker отправляет такие уведомления по email, SMS, голосовым звонком, в Slack, Telegram и другими способами и поддерживает 13 типов проверок. Как это настроить, описано в руководстве по проверке HTTP, остальные руководства по починке собраны в кластере починки, а полный список типов проверок есть на странице возможностей.