cron и crontab
cron планирует команды по выражению из пяти полей (минута час день_месяца месяц день_недели). Пользовательские crontab хранятся в /var/spool/cron; системные задачи — в /etc/cron.d. Окружение cron минимально — без профиля, с голым PATH.
В любой системе есть работа, которая должна повторяться: ночные резервные копии, ежечасная ротация логов, пинг проверки здоровья каждые пять минут. cron решает эту задачу с 1975 года. Ты пишешь строку вида 0 3 * * * /usr/bin/backup.sh — и демон просыпается каждую минуту, проверяет, пора ли запускать задачу, и выполняет её. Просто. Но у cron есть неприятная привычка: он запускает задачи в урезанном окружении — без профиля, без bashrc, с голым PATH, который ничего не знает о /usr/local/bin или твоих language-менеджерах. Скрипт, прекрасно работающий в твоей оболочке, молча провалится в cron именно по этой причине. Понять пять временны́х полей — пять минут обучения; понять ловушку с окружением — экономия часов отладки.
После этого урока ты сможешь написать запись crontab со всеми пятью полями, редактировать свой пользовательский crontab с помощью crontab -e, добавлять системные задачи в /etc/cron.d, использовать сокращения @reboot и @daily, а также избегать ловушки с минимальным PATH, из-за которой ломается большинство первых cron-задач.
Пять временны́х полей управляют тем, когда запускается задача. Строка crontab состоит из пяти временны́х полей и команды:
# м ч дм мес дн команда
# | | | | |
# | | | | +--- день недели (0=Вс, 6=Сб; 7=Вс тоже)
# | | | +-------- месяц (1-12)
# | | +------------- день месяца (1-31)
# | +----------------- час (0-23)
# +-------------------- минута (0-59)
# Запускать в 03:00 каждую ночь:
0 3 * * * /usr/bin/backup.sh
# Запускать на 0-й и 30-й минутах каждого часа (два значения через запятую):
0,30 * * * * /usr/bin/check-health.sh
# Запускать каждые 15 минут (шаг через /):
*/15 * * * * /usr/bin/poll-queue.sh
# Запускать в 08:00 только по будням (Пн-Пт):
0 8 * * 1-5 /usr/bin/morning-report.shЗвёздочка * означает «каждый». Запятая разделяет значения. /N означает «каждые N». Дефис - задаёт диапазон. Этих четырёх конструкций хватает для почти любого расписания.
Редактируй личный crontab с помощью crontab -e. У каждого пользователя есть свой crontab. Демон запускает задачи от имени этого пользователя с его правами — без пароля, без sudo для пользовательских задач.
# Открыть crontab в $EDITOR (по умолчанию vi):
crontab -e
# Показать текущий crontab без редактирования:
crontab -l
# Удалить весь crontab (осторожно — без отмены):
crontab -r
# Редактировать crontab другого пользователя (только root):
crontab -u alice -eФайл хранится в /var/spool/cron/crontabs/<username> — никогда не редактируй его напрямую; всегда используй crontab -e, который проверяет синтаксис перед установкой.
Системные задачи хранятся в /etc/cron.d/ и /etc/crontab. В этих файлах есть шестое поле — имя пользователя, — потому что демону нужно знать, от чьего имени запускать задачу.
# В /etc/crontab и файлах /etc/cron.d/ есть поле USER между временны́ми полями и командой:
# м ч дм мес дн USER команда
0 2 * * * root /usr/sbin/logrotate /etc/logrotate.conf
# В /etc/cron.d/ можно класть отдельные файлы для каждого пакета:
ls /etc/cron.d/
# php apt-compat sysstat unattended-upgrades
# Как выглядят ежедневные/еженедельные/ежемесячные задачи Ubuntu:
ls /etc/cron.daily/ # скрипты, выполняемые run-parts
ls /etc/cron.weekly/
ls /etc/cron.monthly/Директории /etc/cron.daily/ выполняются run-parts — любой исполняемый скрипт там запустится в запланированное время. Синтаксис crontab не нужен: просто положи скрипт и сделай его исполняемым.
@reboot, @daily и другие сокращения заменяют пять полей для типовых расписаний. Это строки-сокращения, которые cron переводит внутренне:
# Эквивалентные сокращения:
@reboot /usr/bin/start-agent.sh # Один раз при старте демона
@hourly /usr/bin/check-disk.sh # 0 * * * *
@daily /usr/bin/nightly-backup.sh # 0 0 * * *
@weekly /usr/bin/weekly-cleanup.sh # 0 0 * * 0
@monthly /usr/bin/monthly-report.sh # 0 0 1 * *
@yearly /usr/bin/audit.sh # 0 0 1 1 *
# @reboot — самое полезное сокращение: заменяет /etc/rc.local для пользовательских
# команд запуска без необходимости создавать юнит systemd.@reboot срабатывает при старте crond, а не строго при загрузке машины — но на Ubuntu с включённым crond они функционально эквивалентны.
Окружение cron минимально — это причина сбоя №1. Когда cron запускает команду, он устанавливает голое окружение: SHELL=/bin/sh, PATH=/usr/bin:/bin, HOME и LOGNAME из файла паролей. Всё. Нет .bashrc, нет .profile, нет nvm, нет pyenv, нет /usr/local/bin.
# Задача, которая молча провалится — 'node' нет в /usr/bin:/bin:
*/5 * * * * node /opt/app/worker.js
# Исправление 1: использовать абсолютный путь к бинарнику:
*/5 * * * * /usr/local/bin/node /opt/app/worker.js
# Исправление 2: переопределить PATH в начале crontab (действует для всех задач ниже):
PATH=/usr/local/bin:/usr/bin:/bin
*/5 * * * * node /opt/app/worker.js
# Исправление 3: явно загрузить профиль в команде:
*/5 * * * * bash -l -c 'node /opt/app/worker.js'
# Перенаправить stdout И stderr в лог-файл для отладки:
*/5 * * * * /usr/local/bin/node /opt/app/worker.js >> /var/log/worker.log 2>&1>> /var/log/worker.log 2>&1 в конце — самое полезное дополнение к любой cron-задаче: без него вывод уходит на почту пользователю (если работает почтовый агент) или молча отбрасывается.
Добавь ежедневный дамп базы данных, который действительно работает в cron.
Цель: дамп PostgreSQL каждый день в 02:30, сохранять в /var/backups/db/, хранить 7 дней файлов.
# Редактировать crontab пользователя postgres:
sudo crontab -u postgres -e
# Добавить эти строки (первые две задают окружение; строка задачи следует за ними):
PATH=/usr/bin:/bin
PGPASSFILE=/var/lib/postgresql/.pgpass
30 2 * * * pg_dump mydb | gzip > /var/backups/db/mydb-$(date +\%Y\%m\%d).sql.gz
35 2 * * * find /var/backups/db/ -name 'mydb-*.sql.gz' -mtime +7 -deleteТри важных момента:
PATHзадан явно —pg_dumpесть в/usr/binна Debian, так что здесь без него обошлось бы, но такая привычка полезна.date +\%Y\%m\%dиспользует\%, потому что%— специальный символ в синтаксисе crontab: он переводится в символ новой строки. Каждый литеральный%в команде crontab должен быть экранирован как\%.- Задача очистки в 02:35 использует
find -mtime +7— файлы старше 7 дней. Запуск через 5 минут после дампа гарантирует, что свежий дамп не будет удалён.
▸Частая ошибка
Ловушка с %. В синтаксисе crontab неэкранированный % переводится в символ новой строки, а всё после первого % становится stdin для команды. Это значит, что date +%Y%m%d в строке crontab превращается в date +, за которым следует новая строка, а потом Y и т.д. как stdin — команда date видит только часть строки формата до первого % и выдаёт неверный результат или ошибку. В crontab всегда пиши \%: date +\%Y\%m\%d. Эта ловушка срабатывает у каждого инженера хотя бы раз — как правило, в скрипте резервного копирования, где испорченное имя файла — единственный симптом.
▸Почему это работает
macOS и cron. На macOS cron есть, но официально объявлен устаревшим с версии macOS 10.4 (Tiger). Замена Apple — launchd и launchctl с файлами .plist в ~/Library/LaunchAgents/ для пользовательских задач. Синтаксис XML-based и многословный, но интегрирован с управлением питанием macOS (задачи могут будить машину). Для кросс-платформенных скриптов, которые должны работать и на macOS-серверах, подумай: нужно ли нативное macOS-расписание, или задачу можно развернуть на Linux-хосте.
Ты пишешь запись crontab: `*/10 * * * * python3 /opt/app/sync.py`. Скрипт отлично работает из твоей оболочки, но в cron никогда не даёт вывода. Какова наиболее вероятная причина?
Запись crontab имеет пять временны́х полей — минута, час, день_месяца, месяц, день_недели — за которыми следует команда. Используй * для «каждый», , для нескольких значений, /N для шага, - для диапазона. Редактируй личный crontab с помощью crontab -e; системные задачи с полем имени пользователя идут в /etc/cron.d/. @reboot и @daily — удобные сокращения для типовых расписаний. Главное правило: cron работает в минимальном окружении — PATH=/usr/bin:/bin, без профиля — поэтому всегда используй абсолютные пути к бинарникам или задавай PATH= в начале crontab. Перенаправляй вывод через >> logfile 2>&1, чтобы сбои были видны. Экранируй литеральный % как \% в командах cron.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.