"Домашнее задание" для компьютера

В настоящей статье будет рассмотрен вопрос о том, как заставить компьютер в указанное время выполнить какую-то задачу без вашего непосредственного участия. Дать ему, так сказать, "домашнее задание", а самому заняться какими-то другими делами, уйти гулять или просто лечь спать.

Уточню, что будет рассматриваться случай, когда компьютер работает под управлением операционной системы Linux (если еще точнее, то рассматривается случай дистрибутива Red Hat 7.3) и для решения поставленной задачи используются только системные средства, а не какие-то специально разработанные программы. Таких средств в Linux три: это демоны crond, anacron и atd, для управления которыми имеются специальные утилиты. Все это хозяйство и будет рассмотрено в данной статье.

Демон, страдающий бессоницей

Давайте начнем с наиболее известного демона crond. В дистрибутив Red Hat 7.3, с которым я экспериментировал при написании данной статьи, входит вариант демона crond, который называется Vixie Cron, по имени его разработчика Пола Викси (Paul Vixie). Но для краткости будем говорить просто "cron".

Системный демон crond предназначен для выполнения регулярно повторяющихся заданий. Обычно crond запускается как системный сервис в процессе начальной загрузки системы и остается активным пока система не выключена. Сразу после старта он просматривает каталоги /var/spool/cron и /etc/cron.d, а также файл /etc/crontab в поисках заданий, которые нужно выполнять. Затем crond просыпается каждую минуту, выполняет предписанные ему задания и снова засыпает до начала следующей минуты.

Чтобы задать cron-у задачу лучше всего воспользоваться командой crontab. Если выполнить ее с опцией -l:


[user]$ crontab -l

то будет выведен текущий список заданий. Если вы еще никаких заданий cron-у не давали, вы увидите в ответ сообщение "no crontab for user". Сформулировать cron-у задачу довольно просто, но, прежде чем пытаться задать ему работу, установите в качестве значения переменной окружения EDITOR указание на любимый (или привычный) текстовый редактор. В противном случае будет вызван редактор vi, с которым я, например, не привык работать. Поэтому я обычно выполняю команду


[user]$ export EDITOR=mcedit

после чего получаю возможность использовать для редактирования списка заданий привычный CoolEdit из пакета Midnight Commander.

После задания значения переменной EDITOR можно выполнить команду


[user]$ crontab -e

по которой будет вызван указанный вами редактор, причем при первом запуске такой команды откроется пустое окно, в котором вы должны сформулировать задание cron-у. Возникает вопрос: а что мешает нам просто запустить любимый редактор, открыть для редактирования нужный файл и внести в него необходимые команды? Этому существует две причины.

Во-первых, файлы, которые хранят задания для cron-а (crontab-файлы), принадлежат root-у и защищены от модификации простыми пользователями. Команда же crontab запускается от имени root-а (для нее установлен так называемый setuid-бит) и имеет доступ к этим файлам. Конечно, если речь идет о вашем персональном компьютере, где вы имеете все права, вам законы не писаны, но лучше все же придерживаться принятых правил игры.

Вторая причина состоит в том, что записи в crontab-файлах должны подчиняться определенным стандартам, быть формализованы, чтобы crond мог их правильно интерпретировать. Команда crontab после того, как вы сохранили вновь отредактированный файл, производит его синтаксический анализ, и, если вы сделали какую-то ошибку, предлагает вам вернуться к его редактированию. Конечно, crontab не может сформулировать за вас задание cron-у, так что правила написания заданий вам необходимо знать. Давайте их рассмотрим.

Каждая строка crontab-файла (кроме строк комментариев, которые обозначаются знаком # в первой позиции) либо устанавливает значение некоторой переменной, либо представляет отдельное задание (ниже приведен пример crontab-файла, можете просмотреть его, чтоб составить первое впечатление).

Строка задания состоит из шести полей, разделяемых пробелами (по крайней мере одним). Первые пять полей отведены для указания времени выполнения задания. В следующей табличке представлены значения, которые можно придавать этим полям

ПолеОпределяетЧисловые значения
1Минуты0-59
2Часы0-23
3Дни месяца1-31
4Месяц1-12 или три первых буквы английского названия месяца (регистр не имеет значения)
5Дни недели0-7 или три первых буквы английского названия дня недели (регистр не имеет значения, а числа 0 и 7 оба обозначают воскресенье)

В каждом из этих полей вместо простого числового значения можно прописать:

Существует также возможность указать, что данное задание должно выполняться только в каждый n-ый час (минуту, день или месяц), для чего в нужном поле записывают примерно такую комбинацию: "*/n" (кавычки, конечно, нужно опустить, а вместо n поставить конкретное число). Эти варианты записи времени выполнения заданий можно комбинировать, но об этом лучше прочитайте на man-странице crontab(5).

Обратите внимание на то, что для указания дня отведено 2 поля: третье и пятое. Если в обоих полях заданы значения, отличные от *, то задание будет выполняться в те дни, когда хотя бы одно из значений дня совпадает с текущим. Например, если в третьем поле стоит 1,15, а в пятом поле - 5 (или FRI), то задание будет выполняться по первым и пятнадцатым числам каждого месяца, а также каждую пятницу (конечно, если в поле месяцев будет стоять *).

Следующее поле (шестое) содержит подлежащую выполнению командную строку оболочки shell. Считается, что поле команда продолжается до конца строки и может содержать пробелы и символы табуляции. Причем заключать эту командную строку в кавычки не требуется. Как и в обычной командной строке shell, можно указать в этом поле несколько команд, разделенных точкой с запятой (хотя, наверное, лучше написать командный файл и указать в crontab-файле его имя).

Задание переменных окружения для cron рассмотрим на примере переменной MAILTO. Надо сказать, что по умолчанию после выполнения каждой строки crontab-файла cron отсылает рапорт о выполнении задания. Содержанием такого рапорта является вывод исполнявшихся команд. Если не задавать переменную MAILTO, то сообщение отправляется тому пользователю, который задал ему данную задачу. Если вы хотите, чтобы сообщения отправлялись не вам, а, скажем, пользователю с именем fred, то в crontab-файле надо записать строку вида

MAILTO="fred"

Боюсь только, что fred не обрадуется, если его почтовый ящик будет заполняться сообщениями от cron. Если они не нужны и вам, то установите переменную MAILTO в нуль:

MAILTO=""

после чего cron не будет загружать вас лишними письмами. Несколько переменных окружения cron устанавливает автоматически при его запуске.

После завершения редактирования вы, как обычно, сохраняете результаты и выходите из редакторской программы. Если вы сделали ошибки, то увидите такое сообщение:


[user] $ crontab -e

crontab: installing new crontab
"/tmp/crontab.10348":0: bad day-of-week
errors in crontab file, can't install.
Do you want to retry the same edit? y

Естественно, придется ответить "y". Если же ошибок не было (или вы их исправили), вы увидите только одну строку:


[user] $ crontab -e

crontab: installing new crontab
что означает, что введенные строки заданий сформулированы корректно (но это вовсе не гарантирует, что в поле команд нет ошибок).

В отличие от других процессов-демонов, которые требуют перезапуска после редактирования их конфигурационных файлов, перезапускать процесс crond после того, как пользователь задал новое задание, не требуется. Дело в том, что просыпаясь ежеминутно, cron проверяет время модификации crontab-файлов и перечитывает те файлы заданий, которые изменялись в последнее время. Поэтому не более чем через минуту ваши задания будут "приняты к исполнению".

Пользоваться услугами crond могут все пользователи, зарегистрированные в системе. Правда, суперпользователь может закрыть эту возможность для некоторых пользователей, прописав их имена в специальный файл /etc/cron.deny, либо же разрешив использовать cron только ограниченному числу пользователей, имена которых перечислены в файле /etc/cron.allow. Подробнее об этом вы можете прочитать на man-странице crontab(1), а пока будем считать, что вам такое право предоставлено.

Где хранятся crontab-файлы?

Я до сих пор преднамеренно не называл конкретного имени crontab-файла, который редактируется по команде crontab -e. Дело в том, что одного такого файла нет. В Linux имеется целый каталог /var/spool/cron, в котором хранятся crontab-файлы для всех пользователей, включая root-а. Каждый такой файл имеет имя, совпадающее с регистрационным именем пользователя, по которому процесс cron определяет, какой UID надо использовать при выполнении команд из этого файла. Владельцем всех этих файлов является пользователь root.

Кроме личных crontab-файлов отдельных пользователей существует также общесистемные crontab-файлы. Один из них, /etc/crontab, находится в каталоге /etc, а остальные - в каталоге /etc/cron.d. Эти файлы не доступны через команду crontab, редактировать их может только суперпользователь. Структура записей в таких файлах тоже несколько отличается от описанной выше: в строках заданий используется дополнительное поле, расположенное перед полем команды. В этом дополнительном поле суперпользователь задает имя пользователя, чей идентификатор (UID) будет использоваться при запуске данного задания. При инсталляции дистрибутива Red Hat создается системный файл /etc/crontab следующего вида:

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/

# run-parts
01 * * * * root run-parts /etc/cron.hourly
02 4 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
42 4 1 * * root run-parts /etc/cron.monthly

Команда run-parts в нем служит для запуска всех скриптов из каталога, указанного в виде параметра этой команды. По большей части эти скрипты выполняют функции по обслуживанию системы: удаляют ненужные временные файлы, присматривают за быстро растущими файлами протоколов в каталоге /var/log и, при необходимости, очищают их, и тому подобное. Обратите внимание на то, что все эти работы (кроме ежечасных) выполняются в 4 часа ночи. Разработчики дистрибутива, по-видимому, имели в виду в первую очередь круглосуточно работающие сервера и установили время выполнения заданий на тот период, когда активность системы минимальна. Ведь большинство этих скриптов интенсивно работают с диском, что может существенно затормозить работу пользователей. Однако такое решение скорее всего непригодно для персонального компьютера, который пока что принято выключать на ночь. Однако, оказывается, что разработчики дистрибутива предусмотрели возможность выключения компьютера на ночь и поручили выполнение некоторых необходимых работ еще и демону anacron.

"Сделал работу и ухожу"

Как уже было отмечено, если некоторое задание не было выполнено демоном crond в указанное время (например, компьютер был выключен), то процесс crond не выполняет такую команду позже, поскольку информация о невыполнении задания ему не поступает. А для некоторых системных заданий такое явление недопустимо.

Эту проблему позволяет решить другой системный демон, имя которого anacron. В отличие от cron, он работает по следующему принципу. При запуске (а запускается он во время старта системы из инициализационных скриптов) он просматривает свой конфигурационный файл (обычно /etc/anacrontab), в котором для каждого задания указывается периодичность (в сутках), с которой должно повторяться выполнение этого задания. Далее anacron проверяет, выполнялось ли данное задание в течение последних n дней. Если нет, anacron запускает на выполнение команду, указанную в строке задания. При этом выполнение команды может осуществляться с некоторой задержкой, величина которой (в минутах) должна быть указана в строке задания. После выполнения задания anacron записывает дату выполнения в специальный файл, содержащий записи о времени последнего выполнения данного задания, чтобы знать, когда надо выполнять это задание снова. Эти файлы сохраняются в каталоге /var/spool/anacron. В файл записывается только дата, часы и минуты не запоминаются.

После выполнения каждого задания anacron посылает сообщение о его выполнении системному демону протоколирования syslogd, а после выполнения всех заданий из конфигурационного файла заканчивает работу.

Конфигурационный файл /etc/anacrontab может содержать строки трех типов: строки описания заданий, строки задания переменных окружения и строки комментариев. Строка описания заданий имеет следующий формат:

          период  задержка  идентификатор_задания  команда

Как уже говорилось, период задается в днях, а задержка - в минутах. Идентификатор задания может содержать любые символы, кроме пробелов и слэшей. Он используется для идентификации задания при формировании сообщений демону протоколирования и при запоминании времени выполнения задания. В качестве команды может использоваться любая команда оболочки.

Строки задания переменных имеют стандартный формат:

          ИМЯ_ПЕРЕМЕННОЙ = ЗНАЧЕНИЕ

В качестве строк комментария может выступать пустая строка, строка состоящая только из пробелов или строка, содержащая произвольную последовательность символов, начинающуюся символом '#' (перед которым может стоять любое количество пробелов).

Приведу в качестве примера файл /etc/anacrontab из стандартной установки дистрибутива Red Hat Linux:

# /etc/anacrontab: configuration file for anacron

# See anacron(8) and anacrontab(5) for details.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# These entries are useful for a Red Hat Linux system.
1	5	cron.daily		run-parts /etc/cron.daily
7	10	cron.weekly		run-parts /etc/cron.weekly
30	15	cron.monthly		run-parts /etc/cron.monthly

Как видите, в Red Hat утилита anacron "подстраховывает" демон cron, запуская периодические задания cron-а, если последний почему-либо их не запускал. Благодаря этому, в частности, скрипт logrotate регулярно (точнее, при каждом запуске компьютера) выполняется, несмотря на то, что демоном crond он не запускается из-за выключения компьютера на ночь. Среди регулярно запускаемых cron-ом скриптов (в каталогах /etc/cron.daily, /etc/cron.weekly и /etc/cron.monthly) вы найдете скрипт 0anacron, который заботится о том, чтобы обновить записи о времени последнего выполнения тех заданий, которые поручены обеим демонам (чтобы исключить их повторное выполнение anacron-ом).

При желании можно, очевидно, сделать так, чтобы cron, в свою очередь, "подстраховывал" anacron, периодически запуская его (хотя проще просто поручить периодические задания cron-у).

Демон anacron запускается при старте системы и, выполнив предписанные ему задания, завершает работу. Наверное поэтому нет никаких утилит, специально предназначенных для ввода новых заданий демону anacron. Такие задания может давать только суперпользователь путем прямого редактирования файла /etc/anacrontab. По-видимому, основное назначение этого демона - выполнять какие-то работы по обслуживанию системы после периодов долгого простоя.

"Жду ваших указаний"

Мы подробно рассмотрели что нужно сделать, чтобы компьютер периодически выполнял какие-то работы без нашего участия. А что, если нужно заставить его выполнить какую-то работу только один раз. Например, выключиться в полночь или еще что-нибудь в этом роде. Для таких случаев в Linux имеется еще один демон - atd, который, так же как и crond, постоянно находится "на посту". В этом вы можете убедиться, выполнив команду


[user] $ ps -ax | grep atd

Для того, чтобы задать работу этому демону, применяются команды at и batch.

Команда at в простейшем случае запускается с единственным параметром - временем выполнения задания:

[user] $ at TIME

После этого появляется следующее предупреждение:

warning: commands will be executed using (in order) a) $SHELL b) login shell c) /bin/sh
at>

и программа ожидает ввода вашего задания. В качестве задания может использоваться любая команда оболочки (в том числе скрипты). После завершения ввода команды надо нажать комбинацию клавиш [Ctrl-D]. В ответ вы увидите сообщение о том, что ваше задание принято под таким-то номером:

job 4 at 2002-09-26 12:15

Вы можете просмотреть очередь ждущих своего времени заданий с помощью команды atq (к сожалению, выводятся только номера ожидающих выполнения работ и время их выполнения, а содержание задания, то есть вызываемая команда, не выводится). Если вы не суперпользователь, вам будут показаны только ваши задания. Суперпользователь видит список заданий, введенных всеми пользователями системы. С помощью команды atrm вы можете удалить задание из этой очереди. Для этого надо указать его номер в качестве параметра команды atrm.

Посмотрим теперь как правильно задать время выполнения вашего задания, то есть правила формирования параметра TIME для команды at. В простейшем варианте указывается только час и минута запуска задания, разделенные двоеточием: "hh:mm". Можно указывать время "в американском стиле", добавив после минут окончание AM (до полудня) или PM (после полудня). Если сегодня указанное время уже прошло, задание будет выполняться завтра. Допускается прямо указать команде at, что задание должно быть выполнено сегодня (at 10:00PM today) или завтра (at 10:00PM tomorrow). Можно также указать дату выполнения задания в формате MMDDYY, или MM/DD/YY, или DD.MM.YY. Допускается использовать название месяца с числовым указанием дня и (необязательным) указанием года. При этом указание на время выполнения в течение заданного дня должно стоять перед указанием даты, например at 10:00PM Jul 31. Можно также указать программе at, что выполнение задания нужно повторить несколько раз. Для этого после указания времени добавляют количество повторений с указанием через какой интервал (час, день, неделя или месяц) надо повторить задание. Например, по команде at 10:00PM + 3 days задание будет выполняться в 10 вечера сегодня и еще в течение 3 дней в то же время. В общем, вариантов указания времени выполнения задания существует множество, их полная спецификация приведена в файле /usr/sharedoc/at-3.1.8/timespec (цифры 3.1.8 обозначают версию утилиты at и у вас могут быть другими).

Как и в случае с демоном crond, суперпользователь может лишить некоторых пользователей права запускать команду at, прописав их имена в специальный файл /etc/at.deny, либо же разрешить использовать at только тем пользователям, имена которых перечислены в файле /etc/at.allow. Причем оболочка сначала ищет файл /etc/at.allow и, если он есть, проверяет наличие вашего имени в нем. Второй файл уже не анализируется. Если же файла /etc/at.allow не существует, проверяется /etc/at.deny, и вам разрешается выполнить at, если ваше имя в этом файле не встречается. Если ни того, ни другого файла не существует, программу at может запускать только суперпользователь. В Red Hat Linux по умолчанию создается пустой файл /etc/at.deny, то есть давать задания демону atd могут все пользователи.

Кроме выполнения заданий в указанное время демон atd может выполнять какие-то работы в периоды низкой загруженности системы. Задания на этот случай формулируются с помощью утилиты batch и ждут своего часа в очереди. Когда загрузка системы снизится до уровня, указанного параметром -l (задается при запуске atd, по умолчанию равен 0.8), задания из очереди запускаются на выполнение.

Какие задачи поручить демонам?

Какое практическое применение имеют эти демоны и нужно ли тратить драгоценные ресурсы на их постоянное присутствие в памяти? Мы уже вообще-то видели, что в стандартной установке дистрибутива Red Hat Linux демоны crond и anacron выполняют некоторые служебные функции. Характерным примером таких задач является задача обслуживания файлов системных протоколов. Как вы знаете, в Linux (как и вообще в UNIX) постоянно ведется несколько протоколов работы, в которых фиксируются те или иные действия как пользователей, так и ядра и других программ и демонов. . И рассматриваемые в статье демоны тоже вносят свои записи в системные протоколы. Отвечает за ведение протоколов системный демон syslogd. Создаваемые им протоколы быстро растут в объеме и, если их своевременно не чистить, могут заполнить все свободное пространство на диске. В стандартной конфигурации Red Hat Linux за ростом протоколов следит скрипт logrotate, расположенный в каталоге /etc/cron.daily. При превышении объема файла протокола некоторого заданного уровня старый файл переименовывается и открывается новый, а считающиеся уже ненужными файлы уничтожаются.

Самым очевидным примером практического использования демона crond является автоматическая выдача напоминаний о приближении каких-либо важных дат или событий. Проще всего организовать такую процедуру с помощью почтового агента (обычно он постоянно запущен на включенном компьютере). Например, почтовый агент kmail из KDE позволяет просматривать сообщения из нескольких "почтовых ящиков". Поэтому достаточно настроить его еще и на получение предназначенных вам писем из почтовой системы на локальном компьютере, указать cron-у когда надо прислать напоминание и вы будете во-время оповещены о приближении любого важного для вас события.

В книге [1] приведено еще несколько примеров использования демона crond для решения таких задач, как:

Я приведу еще один пример практического использования демона crond, причем пример, характерный как раз для персонального (домашнего) компьютера.

Рассмотрим обычную житейскую ситуацию: вы набираете на домашнем компьютере какой-то текст или корректируете исходные тексты программы и в это время отключается электроэнергия. Событие для нашей бытовой сети, к сожалению, обычное. Покупка источника бесперебойного питания вашим семейным бюджетом, конечно, не была предусмотрена, а сохранять результаты работы каждые 15 минут вы тоже, в азарте работы, не удосужились. Я попал в такую ситуацию как раз во время работы над данной статьей. И после восстановления питания последние изменения оказались потеряны, несмотря на то, что как раз перед отключением компьютера я их сохранил и даже вышел из программы редактирования. Это в ДОС сохранение результатов редактирования приводит к тому, что они тут же записываются на диск. А в Linux относительно медленные операции чтения/записи на диск кешируются в оперативной памяти. Это существенно повышает общую производительность системы, но, если система почему-либо рушится, такой подход приводит к тому, что вы потеряете результаты своей работы несмотря на то, что вовремя нажимали кнопочку "Сохранить на диске". Вот тут нам на помощь и может прийти cron!

Идея решения проста. Напомню, что в Linux имеется специальная команда sync, предназначенная для принудительного "сброса" содержимого временных буферов памяти на диск [4]. Давайте будем выполнять ее в автоматическом режиме каждые 15 (или 30) минут. Для этого достаточно прописать в crontab-файл строку следующего вида

0,30 * * * * date; sync; echo "запись данных на диск"
А чтобы сообщения о выполнении этой операции не переполняли ваш почтовый ящик, отправляйте их в никуда (см. выше). Конечно, и самому не надо забывать через каждые полчаса нажимать кнопочку "Сохранить на диске", иначе никакой cron не спасет вас от потери данных при неожиданном выключении питания.

Заключение

Итак, теперь вы в общих чертах знаете, как задать вашему компьютеру "работу на дом" (подробнее смотрите в man-страницах [3]). Дерзайте! Только имейте в виду, что те команды, которые вы прописываете в crontab-файлах, должны быть написаны без ошибок. Дело в том, что если команда задана с ошибкой, результатом ее выполнения, в лучшем случае, будет появление в вашем почтовом ящике сообщения об ошибке. Проснувшись утром вы не обнаружите тех положительных результатов, на которые рассчитывали, задавая с вечера работу вашему электронному другу. Не говоря уж о гораздо более неприятной ситуации, когда увидев результаты выполнения "домашнего задания", вы испытаете желание ударить по компьютеру кувалдой. Помните, что источником всех ошибок является не компьютер, а его владелец. Компьютер пока что не более чем "тупая" машина, которая слепо и точно выполняет все, что вы ему задали. Поэтому, прежде чем прописать задание на ночь, несколько раз проверьте, как оно будет выполняться под вашим присмотром.

Источники и ссылки

  1. Э.Немет, Г.Снайдер, С.Сибасс, Т.Хейн. "UNIX: руководство системного администратора", пер. с англ. С.М.Тимачева, К., BHV, 1999 г., глава 10.
  2. Michael S. Keller, "Take Command: cron: Job Scheduler", LinuxJournal, September 01, 1999
  3. man cron(8), crontab(1), crontab(5), anacron(1), anacrontab(5), syslog(3), at(1), atrun(1), at.deny(5), at.allow(5), atd(8).
  4. man sync(8), sync(2).