Почему мой crontab не работает, и как я могу устранить его?
Это канонический вопрос об использовании cron & crontab.
Вас направили сюда, потому что сообщество достаточно уверено, что ответ на ваш вопрос можно найти ниже. Если на ваш вопрос нет ответа ниже, ответы помогут вам собрать информацию, которая поможет сообществу помочь вам. Эта информация должна быть отредактирована в исходном вопросе.
Ответ: "Почему мой crontab не работает, и как я могу устранить его?'можно увидеть ниже. Это касается cron
Система с выделенным crontab.
7 ответов
Как исправить все ваши проблемы / проблемы, связанные с crontab (Linux)
Это вики сообщества, если вы заметили что-то неправильное в этом ответе или у вас есть дополнительная информация, пожалуйста, отредактируйте ее.
Во-первых, основная терминология:
- cron (8) - это демон, который выполняет запланированные команды.
- crontab (1) - программа, используемая для изменения пользовательских файлов crontab(5).
- crontab(5) - это файл для каждого пользователя, который содержит инструкции для cron(8).
Далее, образование о cron:
Каждый пользователь в системе может иметь свой собственный файл crontab. Расположение корневого и пользовательского файлов crontab зависит от системы, но обычно они ниже /var/spool/cron
,
Существует общесистемный /etc/crontab
файл, /etc/cron.d
директория может содержать фрагменты crontab, которые также читаются и обрабатываются cron. Некоторые дистрибутивы Linux (например, Red Hat) также имеют /etc/cron.{hourly,daily,weekly,monthly}
это каталоги, скрипты внутри которых будут выполняться каждый час / день / неделю / месяц с привилегиями root.
root всегда может использовать команду crontab; обычные пользователи могут или не могут получить доступ. Когда вы редактируете файл crontab с помощью команды crontab -e
и сохраните его, crond проверяет его на правильность, но не гарантирует, что ваш файл crontab сформирован правильно. Есть файл с именем cron.deny
который будет указывать, какие пользователи не могут использовать cron. cron.deny
расположение файла зависит от системы и может быть удалено, что позволит всем пользователям использовать cron.
Если компьютер не включен или демон crond не запущен, а дата / время выполнения команды истекли, crond не выполнит догон и не выполнит прошлые запросы.
Подробности crontab, как сформулировать команду:
Команда crontab представлена одной строкой. Вы не можете использовать \
расширить команду на несколько строк. Хеш (#
) знак представляет комментарий, который означает, что все в этой строке игнорируется cron. Ведущие пробелы и пустые строки игнорируются.
Будьте ОЧЕНЬ осторожны при использовании процентов (%
) войдите в вашу команду. Если они не сбежали \%
они конвертируются в переводы строк и все после первого неотбегавшего %
передается вашей команде на стандартный ввод.
Существует два формата файлов crontab:
Пользователь crontabs
# Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) # | | | | | # * * * * * command to be executed
Система в целом
/etc/crontab
а также/etc/cron.d
фрагменты# Example of job definition: # .---------------- minute (0 - 59) # | .------------- hour (0 - 23) # | | .---------- day of month (1 - 31) # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) # | | | | | # * * * * * user-name command to be executed
Обратите внимание, что последний требует имени пользователя. Команда будет запущена от имени указанного пользователя.
Первые 5 полей строки представляют время, когда команда должна быть запущена. Вы можете использовать числа или, где это применимо, названия дней / месяцев в спецификации времени.
- Поля разделены пробелами или табуляцией.
- Запятая (
,
) используется для указания списка, например, 1,4,6,8, что означает 1,4,6,8. - Диапазоны указаны тире (
-
) и может быть объединен со списками, например 1-3,9-12, что означает от 1 до 3, затем от 9 до 12. /
символ может использоваться для введения шага, например, 2/5, что означает, начиная с 2, затем каждые 5 (2,7,12,17,22...). Они не закрывают конец.- Звездочка (
*
) в поле означает весь диапазон для этого поля (например,0-59
для минутного поля). - Диапазоны и шаги могут быть объединены, например
*/2
означает минимальное значение для соответствующего поля, затем каждые 2, например, 0 для минут ( 0,2...58), 1 для месяцев (1,3 ... 11) и т. д.
Отладка команд cron
Проверьте почту! По умолчанию cron отправляет любые выходные данные команды пользователю, для которого она выполняет команду. Если нет вывода, не будет почты. Если вы хотите, чтобы cron отправлял почту на другую учетную запись, вы можете установить переменную среды MAILTO в файле crontab, например
MAILTO=user@somehost.tld
1 2 * * * /path/to/your/command
Захватите результат самостоятельно
1 2 * * * /path/to/your/command &>/tmp/mycommand.log
который захватывает stdout и stderr в /tmp/mycommand.log
Посмотрите на логи; cron регистрирует свои действия через системный журнал, который (в зависимости от вашей настройки) часто идет в /var/log/cron
или же /var/log/syslog
,
При необходимости вы можете отфильтровать операторы cron, например:
grep CRON /var/log/syslog
Теперь, когда мы рассмотрели основы cron, где находятся файлы и как их использовать, давайте рассмотрим некоторые распространенные проблемы.
Проверьте, что cron работает
Если cron не запущен, ваши команды не будут запланированы...
ps -ef | grep cron | grep -v grep
должен получить что-то вроде
root 1224 1 0 Nov16 ? 00:00:03 cron
или же
root 2018 1 0 Nov14 ? 00:00:06 crond
Если нет, перезапустите его
/sbin/service cron start
или же
/sbin/service crond start
Там могут быть другие методы; используйте то, что обеспечивает ваш дистрибутив.
cron запускает вашу команду в ограниченной среде.
Какие переменные среды доступны, вероятно, будет очень ограниченным. Как правило, вы получите только несколько переменных, таких как $LOGNAME
, $HOME
, а также $PATH
,
Особого внимания заслуживает PATH
ограничено /bin:/usr/bin
, Подавляющее большинство проблем "мой cron скрипт не работает" вызван этим ограничительным путем. Если ваша команда находится в другом месте, вы можете решить эту проблему несколькими способами:
Укажите полный путь к вашей команде.
1 2 * * * /path/to/your/command
Укажите подходящий путь в файле crontab
PATH=/usr:/usr/bin:/path/to/something/else 1 2 * * * command
Если вашей команде требуются другие переменные окружения, вы также можете определить их в файле crontab.
cron запускает вашу команду с помощью cwd == $HOME
Независимо от того, где исполняемая программа находится в файловой системе, текущий рабочий каталог программы при запуске cron будет домашним каталогом пользователя. Если вы обращаетесь к файлам в своей программе, вам необходимо принять это во внимание, если вы используете относительные пути или (предпочтительно) просто везде используете полные пути и избавите всех от путаницы.
Последняя команда в моем crontab не запускается
Cron обычно требует, чтобы команды заканчивались новой строкой. Отредактируйте свой crontab; перейдите в конец строки, содержащей последнюю команду, и вставьте новую строку (нажмите ввод).
Проверьте формат crontab
Вы не можете использовать пользовательский crontab в формате / crontab для / etc / crontab или фрагменты в /etc/cron.d и наоборот. Crontab, отформатированный пользователем, не содержит имя пользователя в 6-й позиции строки, в то время как crontab, отформатированный системой, включает имя пользователя и запускает команду от имени этого пользователя.
Я помещаю файл в /etc/cron. enjhourly,daily,weekly,monthly}, и он не запускается
- Убедитесь, что имя файла не имеет расширения, см. Run-parts
- Убедитесь, что файл имеет разрешения на выполнение.
- Сообщите системе, что использовать при выполнении вашего скрипта (например, put
#!/bin/sh
наверху)
Cron даты, связанные с ошибками
Если ваша дата была недавно изменена в результате обновления пользователя или системы, часового пояса или другого, то crontab начнет работать неправильно и выдает странные ошибки, иногда работающие, иногда нет. Это попытка crontab попытаться "сделать то, что вы хотите", когда время изменится из-под него. Поле "минуты" станет недействительным после смены часа. В этом случае принимаются только звездочки. Перезапустите cron и попробуйте снова, не подключаясь к Интернету (чтобы у даты не было возможности перезагрузить один из серверов времени).
Знаки процента, опять
Чтобы подчеркнуть совет о знаках процента, вот пример того, что cron делает с ними:
# cron entry
* * * * * cat >$HOME/cron.out%foo%bar%baz
создаст файл ~/cron.out, содержащий 3 строки
foo
bar
baz
Это особенно навязчиво при использовании date
команда. Обязательно избегайте знаков процента
* * * * * /path/to/command --day "$(date "+\%Y\%m\%d")"
Debian Linux и его производные (Ubuntu, Mint и т. Д.) Имеют некоторые особенности, которые могут помешать выполнению ваших заданий cron; в частности, файлы в /etc/cron.d
, /etc/cron.{hourly,daily,weekly,monthly}
должен:
- быть владельцем root
- быть доступным для записи только пользователю root
- не может быть доступно для записи группе или другим пользователям
- иметь имя без точек "." или любой другой специальный символ, кроме "-" и "_".
Последний ранит регулярно ничего не подозревающих пользователей; в частности, любой скрипт в одной из этих папок с именем whatever.sh
, mycron.py
, testfile.pl
и т.д. не будут выполнены, никогда.
По моему опыту, этот конкретный момент был наиболее частой причиной невыполнения cronjob в Debian и его производных.
Увидеть man cron
для более подробной информации, если это необходимо.
Если ваши cronjobs перестают работать, проверьте, что срок действия вашего пароля не истек., Поскольку после этого все задания cron прекращаются.
Будут сообщения в /var/log/messages
аналогично приведенному ниже, который показывает проблемы с аутентификацией пользователя:
(username) FAILED to authorize user with PAM (Authentication token is no longer valid; new one required)
Необычные и нерегулярные графики
Cron - это все, что считается очень простым планировщиком, а синтаксис не позволяет администратору легко формулировать немного более необычные графики.
Рассмотрим следующую работу, которая обычно объясняется как "запустить command
каждые 5 минут ":
*/5 * * * * /path/to/your/command
против:
*/7 * * * * /path/to/your/command
который не всегда работает command
каждые 7 минут.
Помните, что символ / можно использовать для введения шага, но шаги не переносятся за пределы конца ряда, например */7
который соответствует каждой седьмой минуте из минут 0-59
т.е. 0,7,14,21,28,35,42,49,56, но между одним часом и следующим будет только 4 минуты между партиями, после 00:56
новая серия начинается в 01:00
, 01:07
и т. д. (и партии не будут работать на 01:03
, 01:10
, 01:17
так далее.).
Что делать вместо этого?
Создать несколько партий
Вместо одного задания cron создайте несколько пакетов, которые в результате приведут к желаемому расписанию.
Например, чтобы запускать пакет каждые 40 минут (00:00, 00:40, 01:20, 02:00 и т. Д.), Создайте две партии: одну, которая запускается дважды в четные часы, и вторую, которая запускает только нечетные часы:
# The following lines create a batch that runs every 40 minutes i.e.
# runs on 0:00, 0:40, 02:00, 02:40 04:00 etc to 22:40
0,40 */2 * * * /path/to/your/command
# runs on 01:20, 03:20, etc to 23:20
20 1/2 * * * /path/to/your/command
# Combined: 0:00, 0:40, 01:20, 02:00, 02:40, 03:20, 04:00 etc.
Запускайте свои партии реже
Вместо того, чтобы запускать пакет каждые 7 минут, что является сложным графиком для разбивки на несколько пакетов, просто запустите его каждые 10 минут.
Запускайте свои партии чаще (но не допускайте одновременного запуска нескольких партий)
Многие нечетные расписания развиваются, потому что время выполнения пакета увеличивается / колеблется, а затем партии планируются с небольшим дополнительным запасом прочности, чтобы предотвратить одновременное наложение и запуск последующих запусков одной и той же партии.
Вместо этого думайте по-другому и создайте cronjob, который изящно потерпит неудачу, когда предыдущий прогон еще не закончился, но который будет работать иначе. Смотрите этот Q & A:
* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job
Это почти сразу же начнет новый запуск после того, как завершится предыдущий запуск /usr/local/bin/ частый_cron_job.
Начните свои партии чаще (но выходите изящно, когда условия не правильны)
Поскольку синтаксис cron ограничен, вы можете решить разместить более сложные условия и логику в самом пакетном задании (или в сценарии-оболочке вокруг существующего пакетного задания). Это позволяет вам использовать расширенные возможности ваших любимых языков сценариев, комментировать ваш код и предотвращать трудно читаемые конструкции в самой записи crontab.
В баш seven-minute-job
будет выглядеть примерно так:
#!/bin/bash
# seven-minute-job
# This batch will only run when 420 seconds (7 min) have passed
# since the file /tmp/lastrun was either created or updated
if [ ! -f /tmp/lastrun ] ; then
touch /tmp/lastrun
fi
if [ $(( $(date +%s) - $(date -r /tmp/lastrun +%s) )) -lt 420 ] ; then
# The minimum interval of 7 minutes between successive batches hasn't passed yet.
exit 0
fi
#### Start running your actual batch job below
/path/to/your/command
#### actual batch job is done, now update the time stamp
date > /tmp/lastrun
#EOF
Который вы можете затем безопасно (попытаться) запустить каждую минуту:
* * * * * /path/to/your/seven-minute-job
Другая, но похожая проблема заключалась бы в том, чтобы запланировать запуск пакета в первый понедельник каждого месяца (или во вторую среду) и т. Д. Просто запланируйте запуск пакета каждый понедельник и выходите, если дата не находится между 1- м или 7- м и день недели не понедельник.
#!/bin/bash
# first-monday-of-the-month-housekeeping-job
# exit if today is not a Monday (and prevent locale issues by using the day number)
if [ $(date +%u) != 1 ] ; then
exit 0
fi
# exit if today is not the first Monday
if [ $(date +%d) -gt 7 ] ; then
exit 0
fi
#### Start running your actual batch job below
/path/to/your/command
#EOF
Который вы можете безопасно (попытаться) запустить каждый понедельник:
0 0 * * 1 /path/to/your/first-monday-of-the-month-housekeeping-job
Не используйте cron
Если ваши потребности сложны, вы можете рассмотреть возможность использования более продвинутого продукта, который предназначен для запуска сложных расписаний (распределенных по нескольким серверам) и который поддерживает триггеры, зависимости заданий, обработку ошибок, повторные попытки и мониторинг повторных попыток и т. Д. msgstr "планирование работы и / или" автоматизация рабочей нагрузки ".
PHP конкретные
Если у вас есть работа cron, например:
php /bla/bla/something.php >> /var/logs/somelog-for-stdout.log
И в случае ошибок ожидайте, что они будут отправлены вам, а они нет - проверьте это.
PHP по умолчанию не отправляет ошибки в STDOUT. @ смотри https://bugs.php.net/bug.php?id=22839
Чтобы исправить это, добавьте в файл php.ini или в свою строку (или в свою оболочку bash для PHP) эти:
- --define display_startup_errors = 1
- --define display_errors = 'stderr'
Первая настройка позволит вам иметь летальные исходы, такие как "Memory oops", а вторая - перенаправить их всех в STDERR. Только после того, как вы сможете хорошо выспаться, все будет отправлено на почту вашего root, а не только в журнал
Добавив мой ответ отсюда для полноты, и добавив еще один потенциально полезный ресурс:
cron
у пользователя другой $PATH
чем вы
Частая проблема, которую пользователи делают с crontab
записи в том, что они забывают, что cron
работает в другом environment
чем они, как зарегистрированный пользователь. Например, пользователь создает программу или скрипт в своем $HOME
каталог и вводит следующую команду для его запуска:
$ ./certbot ...
Команда отлично запускается из его командной строки. Затем пользователь добавляет эту команду к своему crontab
, но находит, что это не работает:
*/10 * * * * ./certbot ....
Причина неудачи в этом случае заключается в том, что ./
это другое место для cron
пользователя, чем для вошедшего в систему пользователя. Это environment
это отличается! ПУТЬ является частью environment
и это обычно отличается для cron
пользователь. Усложняет эту проблему то, что environment
за cron
это не то же самое для всех дистрибутивов * nix, и есть несколько версий cron
Простое решение этой конкретной проблемы состоит в том, чтобы дать cron
Укажите полную спецификацию пути в crontab
запись:
0 22 * * * /path/to/certbot .....
Что cron
пользователя environment
?
В некоторых случаях нам может понадобиться environment
спецификация для cron
в нашей системе (или нам может быть просто любопытно). Что environment
для cron
пользователь, а чем он отличается от нашего? Кроме того, нам может понадобиться знать environment
для другого cron
пользователь - root
например... что такое root
пользователя environment
с помощью cron
? Один из способов узнать это - спросить cron
сказать нам:
- Создайте сценарий оболочки в вашем домашнем каталоге (
~/
) следующим образом (или с редактором по вашему выбору):
$ nano ~/envtst.sh
- Введите следующее в редакторе после настройки для вашей системы / пользователя:
#!/bin/sh
/bin/echo "env report follows for user "$USER >> /home/you/envtst.sh.out
/usr/bin/env >> /home/you/envtst.sh.out
/bin/echo "env report for user "$USER" concluded" >> /home/you/envtst.sh.out
/bin/echo " " >> /home/you/envtst.sh.out
- Сохраните файл, выйдите из редактора и установите права доступа к файлу как исполняемые.
$ chmod a+rx ~/envtst.sh
- Запустите только что созданный скрипт и просмотрите результат в
/home/you/envtst.sh.out
, Этот вывод покажет вашу текущую среду как$USER
Вы вошли как:
$ ./envtst.sh $$ cat /home/you/envtst.sh.out
- Открыть свой
crontab
для редактирования:
$ crontab -e -u root
- Введите следующую строку в нижней части вашего
crontab
:
* * * * * /home/you/envtst.sh >> /home/you/envtst.sh.err 2>&1
ОТВЕТ: выходной файл /home/you/envtst.sh.out
будет содержать список environment
для "пользователя root cron". Как только вы это знаете, настройте crontab
запись соответственно.
Я не могу указать график, который мне нужен в моем crontab
запись:
Запись в расписании для crontab
конечно определяется в man crontab
, и вы должны прочитать это. Тем не менее, чтение man crontab
и понимание графика две разные вещи. И метод проб и ошибок в спецификации расписания может стать очень утомительным. К счастью, есть ресурс, который может помочь: гуру crontab., Введите спецификацию вашего расписания, и оно объяснит расписание на простом английском языке.
Наконец, и рискуя оказаться лишним с одним из других ответов, не думайте, что вы ограничены одним crontab
запись, потому что у вас есть одна работа, чтобы запланировать. Вы можете использовать как можно больше crontab
записи, как вам нужно, чтобы получить расписание, которое вам нужно.
Как сказал @Seamus, 99% моих проблем с crontab возникают из-за наличия разных переменных среды или разных переменных, поэтому crontab не может найти сценарий и происходит сбой молча. Поэтому моим решением было написать сценарий-обертку, который бы:
- Устанавливает
PATH
переменная к той же, к которой я привык - Устанавливает мой
PYTHONPATH
на мой собственный, чтобы включить все мои общие функции - Изменяет
DISPLAY
ИDBUS
переменные, чтобы приложения с графическим интерфейсом, такие как сообщения notify-send, действительно выводились на экран - Изменяет текущий рабочий каталог на тот же, что и скрипт. (Если это применимо)
- Запускает скрипт с помощью
nice ionice -c3
для экономии ресурсов. - Регистрирует
stdout
иstderr
сценария в каталоге «logs», чтобы, если что-то пойдет не так, я мог действительно узнать, что произошло.
Теперь, если я хочу запустить скрипт, я просто редактирую mycrontab с помощью crontab -e, чтобы:
mm hh * * * /path-to.../cron.launcher.py script.name.sh
и он запускает сценарий так же, как если бы я пытался это сделать с терминала, без каких-либо ухудшений. Изначально это был BASH-скрипт, но он зациклился на аргументах с пробелами, поэтому я переписал все на Python. Он даже запустит приложения с графическим интерфейсом в пользовательском пространстве:
#!/usr/bin/python3
# Runtime directories:
SYSPATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"
PYTHONPATH = ""
################################################################################
import os
import time
import sys
import subprocess
# Get username passed by crontab
user = os.environ['LOGNAME']
uid = subprocess.check_output(('id -u ' + user).split()).decode().strip()
gid = subprocess.check_output(('id -g ' + user).split()).decode().strip()
# Set Environmental Variables:
os.environ["PATH"] = SYSPATH
os.environ["PYTHONPATH"] = PYTHONPATH
os.environ["DISPLAY"] = ":0"
os.environ["XAUTHORITY"] = os.path.join('/home', user, '.Xauthority')
os.environ["DBUS_SESSION_BUS_ADDRESS"] = 'unix:path=' + os.path.join('/run/user', uid, 'bus')
os.environ["XDG_RUNTIME_DIR"] = os.path.join("/run/user", uid)
# Get args:
script = sys.argv[1]
args = sys.argv[2:]
name = os.path.basename(script)
basedir = os.path.dirname(sys.argv[0])
# Log dir
logdir = os.path.join(basedir, 'cron.logs')
log = os.path.join(logdir, name + '.' + str(int(time.time())) + '.log')
os.makedirs(logdir, exist_ok=True)
if not os.access(logdir, os.W_OK):
print("Can't write to log directory", logdir, file=sys.stderr)
sys.exit(1)
# If running as root, lock up any scripts so others can't edit them before root runs them.
if os.geteuid() == 0:
cron_log = os.path.join(logdir, 'cron.launcher.root.log')
for path in [script, sys.argv[0]] + list(filter(None, PYTHONPATH.split(':'))):
print("Setting permisisons for", path)
subprocess.run(('chown root -R ' + path).split(), stderr=subprocess.PIPE, check=False)
subprocess.run(('chmod og-w -R ' + path).split(), stderr=subprocess.PIPE, check=False)
else:
cron_log = os.path.join(logdir, 'cron.launcher.log')
cron_log = open(cron_log, 'a')
logger = lambda *args: cron_log.write(' '.join(map(str, args)) + '\n')
# Run the script
output = open(log, mode='w')
if os.path.exists(script):
os.chdir(os.path.dirname(script))
cmd = ['nice', 'ionice', '-c3'] + [script] + args
logger(time.strftime('\n%m-%d %H:%M', time.localtime()).lstrip('0'), user, "running:")
logger(cmd)
logger("in directory", os.getcwd(), "with log in", log)
start = time.time()
ret = subprocess.run(cmd, stdout=output, stderr=output, check=False)
# Cleanup
fmt = lambda num: ("{0:.3g}").format(num) if abs(num) < 1000 else str(int(num))
logger("Returned", ret.returncode, "after", fmt(time.time() - start), 'seconds')
output.close()
cron_log.close()
Обязательно дважды проверьте свой$PATH
и$PYTHONPATH
переменные на случай, если вам понадобится отредактировать их, чтобы они были разными.