open atlas
↑ К треку
Linux: операционная система LIN · 11 · 01

cron и crontab

cron планирует команды по выражению из пяти полей (минута час день_месяца месяц день_недели). Пользовательские crontab хранятся в /var/spool/cron; системные задачи — в /etc/cron.d. Окружение cron минимально — без профиля, с голым PATH.

LIN Junior ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

В любой системе есть работа, которая должна повторяться: ночные резервные копии, ежечасная ротация логов, пинг проверки здоровья каждые пять минут. 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-задач.

1

Пять временны́х полей управляют тем, когда запускается задача. Строка 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». Дефис - задаёт диапазон. Этих четырёх конструкций хватает для почти любого расписания.

2

Редактируй личный 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, который проверяет синтаксис перед установкой.

3

Системные задачи хранятся в /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 не нужен: просто положи скрипт и сделай его исполняемым.

4

@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 они функционально эквивалентны.

5

Окружение 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

Три важных момента:

  1. PATH задан явно — pg_dump есть в /usr/bin на Debian, так что здесь без него обошлось бы, но такая привычка полезна.
  2. date +\%Y\%m\%d использует \%, потому что % — специальный символ в синтаксисе crontab: он переводится в символ новой строки. Каждый литеральный % в команде crontab должен быть экранирован как \%.
  3. Задача очистки в 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-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено

Что-то непонятно?

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

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.