Почему мой 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. Укажите полный путь к вашей команде.

    1 2 * * * /path/to/your/command
    
  2. Укажите подходящий путь в файле 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 сказать нам:

  1. Создайте сценарий оболочки в вашем домашнем каталоге (~/) следующим образом (или с редактором по вашему выбору):
$ nano ~/envtst.sh
  1. Введите следующее в редакторе после настройки для вашей системы / пользователя:
#!/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
  1. Сохраните файл, выйдите из редактора и установите права доступа к файлу как исполняемые.
$ chmod a+rx ~/envtst.sh
  1. Запустите только что созданный скрипт и просмотрите результат в /home/you/envtst.sh.out, Этот вывод покажет вашу текущую среду как $USER Вы вошли как:
$ ./envtst.sh $$ cat /home/you/envtst.sh.out
  1. Открыть свой crontab для редактирования:
$ crontab -e -u root
  1. Введите следующую строку в нижней части вашего 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 не может найти сценарий и происходит сбой молча. Поэтому моим решением было написать сценарий-обертку, который бы:

  1. УстанавливаетPATHпеременная к той же, к которой я привык
  2. Устанавливает мойPYTHONPATHна мой собственный, чтобы включить все мои общие функции
  3. ИзменяетDISPLAYИDBUSпеременные, чтобы приложения с графическим интерфейсом, такие как сообщения notify-send, действительно выводились на экран
  4. Изменяет текущий рабочий каталог на тот же, что и скрипт. (Если это применимо)
  5. Запускает скрипт с помощьюnice ionice -c3для экономии ресурсов.
  6. Регистрирует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переменные на случай, если вам понадобится отредактировать их, чтобы они были разными.

Другие вопросы по тегам