- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF136:Bash
Материал из Linuxformat.
- Bash Извернитесь в Linux, сэкономив время и упростив себе жизнь
Содержание |
Bash: Осваиваем init-файлы
Bash. Рэйчел Проберт |
---|
Bash. Ник Вейч |
---|
|
- Создайте себе скрипт init.d и процессы-демоны для слежения за доступностью сети. Ник Вейч проведет вас по катакомбам sysvinit.
На данном уроке мы создадим скрипт инициализации. А вы-то думали, это учебник по Bash? Ну, это само собой. Скрипты инициализации системы для всех версий и вариантов Linux (ну, почти для всех) пишутся для системной оболочки, а в большинстве дистрибутивов она и есть Bash. Так что написание скриптов инициализации – это программирование в Bash.
Вся ваша ОС Linux построена вокруг Bash, и это наиболее очевидно при взгляде в каталог /etc. Для более сложных процедур некоторые дистрибутивы применяют Python, но последовательность загрузки все равно работает в Bash, и все службы и приложения запускаются ею через скрипт инициализации.
Вы можете взять любой скрипт Bash, поместить его в /etc/init.d и заставить запускаться при загрузке. Однако для стартовых скриптов принят минимальный стандарт. Они обязаны принимать параметр, описывающий выполняемое действие. На самом базовом уровне это команды start, stop и status. В данном учебнике мы еще не сталкивались со вводом, так что давайте изучим его сейчас. Создайте следующий скрипт и сохраните его под именем script1.sh:
#!/bin/bash echo You gave me the parameter $1
Теперь смотрите, что произойдет при его запуске:
> sh script2.sh You gave me the parameter > sh script2.sh plop You gave me the parameter plop > sh script2.sh plop plopster You gave me the parameter plop
Переменная $1 содержит первый параметр, передаваемый скрипту при его запуске. Из примеров видно, что разделителем параметров служит пробел. Второй параметр был бы $2, третий – $3, и так далее.
В данном случае
Теперь займемся обработкой введенного. Как и большинство скриптовых языков (исключение – Python), Bash содержит конструкцию выбора [case], позволяющую выполнять определенные куски кода в зависимости от значения некой переменной. Именно это и нужно для определения цели запуска скрипта, и если вы просмотрите скрипты инициализации вашей системы, то увидите, что они все используют эту структуру.
Итак, все, что необходимо сделать – это создать структуру и описать все возможные исходы:
#!/bin/bash case “$1” in start) echo $”I would if I knew how” ;; stop) echo $”Yes, stopped” ;; status) echo $”Not sure” ;; *) echo $”Usage: $0 {start|stop|status}” esac
Структура очень проста, хотя и осложняется склонностью Bash к экзотической символике. После ключевого слова case следует переменная-переключатель, а затем in. Каждое рассматриваемое значение завершается закрывающей скобкой, чтобы отделить его от выполняемого кода. Затем эти блоки кода завершаются сдвоенным символом точки с запятой.
В данном примере, значение *) – это оставшиеся варианты, т. е. «все то, что не перечислено», и в данном случае это означает вывод справки по использованию.
Из кода видно, что мы сослались на $0 – это сама вызванная команда. Оператор выбора замыкается, как и большинство конструкций Bash (кроме do), ключевым словом, записанным наоборот.
Желая создать простейший скрипт, мы могли бы просто разместить вызов требуемой команды в секции start кода. Но для скрипта инициализации это не совсем хорошо – следует подумать о способах его использования. Вдруг он будет запущен дважды? Все очень запуталось бы. И как узнать, который процесс остановить? Эх, нам бы способ попроще...
Постоянные читатели знают, что такие мои слова обычно означают наличие неких уловок, помогающих отвертеться от трудов, и этот раз – не исключение. Исключение – то, что это не трюк; мы воспользуемся инструментами, которые и предполагается пользоваться!
В вашей системе спрятан небольшой скрипт, выполняющий все основные задачи, необходимые скриптам инициализации. Вообще-то в каждом дистрибутиве он свой, и это несколько осложняет дело. К счастью, усилия замечательных людей по созданию Linux Standard Base [LSB, Базы стандартов Linux] не пропали зря, и имеется стандартизованная версия этого скрипта – ее можно найти в /lib/lsb/init-functions. Вы не обязаны использовать ее – в Fedora я предпочитаю стандартный скрипт functions из каталога init.d, но в целях создания скрипта, работоспособного в любом дистрибутиве, я должен считаться с LSB.
Эту трудосберегающую часть проекта Bash нет нужды даже вставлять в ваш собственный скрипт. Bash имеет средства включения других скриптов при помощи ключевого слова source или .. Все, что надо сделать – это добавить следующую строку в начало соответствующего кода:
. /lib/lsb/init-functions
Теперь в строке скрипта после ключа start) мы можем вызвать одну из вспомогательных функций, чтобы что-то запустить:
start_daemon /bin/echo “I have started so I’ll finish”
Команда echo – не из тех, что вам хотелось бы сделать демоном, но здесь она взята просто для примера. Грубо говоря, здесь можно запустить что угодно.
Шаблоны вариантов
Если вы желаете поэкспериментировать с case, можете воспользоваться шаблонами [pattern] для описания желаемого варианта.
- ?( pattern1 | pattern2 | ...) ни одного или одно вхождение любого из шаблонов
- *( pattern1 | pattern2 | ...) ноль или более вхождений любого из шаблонов
- @( pattern1 | pattern2 | ...) в точности одно вхождение любого из шаблонов
- +( pattern1 | pattern2 | ...) одно или более вхождений любого из шаблонов
Что же запускать?
Окружение инициализации разработано для запуска демонов – процессов, работающих сами по себе в фоновом режиме. Я предполагал создать скрипт для чего-нибудь вроде torrent-клиента без оболочки, но, пробежавшись по Интернету, обнаружил, что большинство из них ее имеют.
Однако в учебных целях и для тестирования мило было бы запустить службу собственного изобретения – в данном случае, другой скрипт Bash.
Вот простой скрипт, принимающий две вещи: IP-адрес и файл журнала. Угадаете ли вы, что он делает?
#!/bin/bash ADDRESS=$1 LOGFILE=$2 while true do if /bin/ping -c 2 $ADDRESS > /dev/null then echo `date` $ADDRESS online >> $LOGFILE else echo `date` $ADDRESS offline >> $LOGFILE fi sleep 60 done
Смысл цикла очевиден, а конструкция if может вас озадачить. Вы помните: то, что стоит после if, должно быть true или false. В данном случае это действующая команда. Как же она может быть истиной или ложью?
Ха-ха, а она возвращает значение! Команда ping возвращает true при успешном завершении (ответ получен) и false в противном случае.
Итак, в зависимости от результата, скрипт выполняет ту или иную команду echo. В прошлый раз мы лишь слегка коснулись использования обратного штриха ` (расположен обычно слева от клавиши 1) в скриптах Bash. В такие кавычки заключаются команды, которые должны выполняться «на месте», с подстановкой вывода обратно в скрипт. В данном случае мы получаем время. Результат действия этих строк в том, что время, адрес и статус соединения добавляется в указанный файл журнала. Команда sleep заставляет скрипт ждать 60 секунд до следующей попытки.
В некоторых обстоятельствах этот скрипт бывает весьма полезен. Его можно использовать в качестве простого монитора сервера, или для проверки доступности сетевого соединения.
Попробуйте запустить его самостоятельно в командной строке, указав какие-нибудь значения. Скрипт нескончаемый, и для его остановки потребуется нажать Ctrl+C.
Запускаем свою службу
Теперь мы можем просто запустить наш скрипт прямо из скрипта инициализации, но необходимо не допустить повторного запуска, а также знать, как его остановить. Как это сделать? Древние мудрецы Unix, долго-долго распивая кофе и оглаживая бороды, пришли к мысли о pid-файле. При каждом запуске процессу присваивается номер. Проверьте это, введя команду ps:
> ps PID TTY TIME CMD 8341 pts/1 00:00:00 su 8353 pts/1 00:00:00 bash 19369 pts/1 00:00:00 ps
Число в первом столбце – уникальный ID процесса, который, среди прочего, можно использовать для слежения за процессом, посылки ему сигналов и его остановки.
Pid-файл – просто файл, содержащий этот номер (или, потенциально, несколько номеров), а также удобный способ узнать, запущен ли конкретный процесс, и если да, то где он. По соглашению, pid-файл хранится в /var/run/<имя>.pid.
Создание pid-файла – дело непростое, но нам поможет система. В Debian и его производных (это про вас, убунтоиды) имеется исполняемый файл по имени start-stop-daemon, и мы им воспользуемся. В Fedora можно подключить скрипт /etc/init.d/functions и использовать вместо этого daemon. Кроме того, для создания pid-файла сгодится и сам скрипт. Bash узнает номер своего собственного процесса из внутренней переменной $$, так что можно просто выполнить команду
echo $$ > /var/run/netcheckd.pid
При запуске прямо из скрипта она создаст желаемый pid-файл. Но далее мы будем держаться способа Debian/Ubuntu.
Воспользовавшись start-stop-daemon, мы можем создавать pid-файл сразу после запуска нашего скрипта. Нужно также передать ему несколько параметров, чтобы сообщить, какую операцию следует выполнить:
start-stop-daemon --start -b --pidfile /var/run/netcheckd.pid --make-pidfile --exec /usr/sbin/netcheck 192.168.0.1 /home/evilnick/netcheck.log
Ключ --start сообщает загрузчику, какую программу мы хотим запустить. Ключ --pidfile определяет расположение pid-файла. Добавление --make-pidfile приводит к созданию этого файла (c PID внутри), если он не существует, а далее после --exec идет команда, которую мы хотим выполнить.
Ключ -b нам надо использовать, поскольку мы запускаем другой скрипт. Обычно двоичная команда разветвляет свой процесс так, чтобы работать в фоне. Скрипты Bash этого не делают, и если мы не запустим наш скрипт в фоновом режиме, init-скрипт никогда не завершится. Это не здорово, поскольку означает, что надо добавить собственные проверки для слежения за процессом, но не так уж трудно.
Итак, теперь мы можем изменить начальный раздел следующим образом:
start) if [ -f $PIDFILE ]; then log_failure_msg “Process running already” exit 1 fi start-stop-daemon --start -b \ --pidfile $PIDFILE --make-pidfile \ --exec $NAME $ADDRESS $LOGFILE ;;
В начальном разделе кода проверяется существование pid-файла, и если он есть, то перед завершением выдается сообщение об ошибке. Если файл не существует, мы можем запустить наш демон.
Вы можете заметить, что все сопутствующие детали заменены переменными. Мы хотим аккуратно обращаться со значениями и легко находить их, поэтому выделим их и разместим в начале файла.
Включение LSB – большое дело для дистрибутивов, но они также должны соответствовать некоторым стандартам, один из которых – большой блок-комментарий в начале скриптов инициализации:
### BEGIN INIT INFO # Provides: netcheckd # Required-Start: $local_fs $remote_fs $network # Required-Stop: $local_fs $remote_fs $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: manage the network check script ### END INIT INFO NAME=netcheck SCRIPT=/usr/bin/$NAME PIDFILE=/var/run/$NAME.pid ADDRESS=192.168.0.1 LOGFILE=/var/log/${NAME}.log
Конечно же, вы можете изменить эти значения на подходящие вам. Поскольку по умолчанию скрипт запускается от имени root, он имеет доступ к каталогу /var для создания pid-файла и журнала. Вы можете пожелать запустить демон от имени другого пользователя – и это возможно, но тогда файл журнала следует хранить где-то в другом месте.
Остановите его
Итак, мы запустили нашу команду – а как ее остановить? Pid-файл предоставляет нам номер процесса работающего скрипта, а зная номер, можно просто уничтожить процесс командой kill. Затем не забудьте очистить и удалить pid-файл, чтобы демон можно было запустить вновь:
stop) if [ -f $PIDFILE ]; then PID=`cat $PIDFILE` #проверяем запущен ли процесс if `ps $PID >/dev/null` ; then kill -HUP $PID log_success_msg “Process stopped” else log_failure_msg “Process not running” fi # удаляем Pid-файлв любом случае rm $PIDFILE else log_failure_msg “Process not running” fi ;;
Здесь мы проверяем, что pid-файл существует, иначе считаем, что процесс не запущен. Затем извлекаем ID процесса при помощи магии обратного апострофа и проверяем командой ps, что он действительно существует. Если существует, то уничтожаем [kill] его.
Применение команды kill – это сурово. Можно воспользоваться сигналом SIGHUP: он вежливо попросит процесс умереть. Для нашего скрипта этого будет достаточно (он отработает остаток выполняющейся команды sleep и закроется).
Кроме того, перед выходом следует удалить pid-файл. Это не самый надежный скрипт – следовало бы проверить, сработала ли команда kill, а уж потом трубить сигнал об успешном выполнении. Это означает ожидание и дополнительную проверку списка процессов, а добавлять ее или нет – решать вам.
Мы также должны добавить указатель статуса. Это сводится лишь к небольшой переработке блока stop:
status) if [ -f $PIDFILE ]; then PID=`cat $PIDFILE` #проверяем, запущен ли процесс if `ps $PID >/dev/null` ; then log_success_msg “Process running” else log_warn_msg “Process has failed... removing pidfile” rm $PIDFILE fi else log_success_msg “Process not running” fi ;;
Тут мы добавили проверку корректности pid-файла. Если все выглядит так, будто процесс запущен, но он не обнаруживается, мы просто удаляем устаревший pid-файл. Такое бывает в случае некорректного выключения машины или если какая-то команда убила наш скрипт.
Другая часто реализуемая опция в таких скриптах – restart [перезагрузка]. В простейшем случае это операция stop, за которой следует операция start, и вы можете легко выделить каждое действие в функцию, а затем просто объединить их вместе в restart.
Вы найдете полную версию скрипта слежения за сетью на нашем сайте http://www.linuxformat.com/files/136bash.zip.
Если запустить полный скрипт локально, вы должны увидеть
>sh netcheckd status * Process not running > sh netcheckd start * netcheck started > sh netcheckd status * Process running > sh netcheckd stop * Process stopped > netcheckd restart * Process not running * netcheck started >
Осталось поместить файлы в должные каталоги (/etc/init.d/netcheckd, /usr/bin/netcheck) и, если пожелаете, добавить их в список автозапуска. В Ubuntu для этого просто выполните:
>sudo update-rc.d netcheckd defaults Adding system startup for /etc/init.d/netcheckd ... /etc/rc0.d/K20netcheckd -> ../init.d/netcheckd /etc/rc1.d/K20netcheckd -> ../init.d/netcheckd /etc/rc6.d/K20netcheckd -> ../init.d/netcheckd /etc/rc2.d/S20netcheckd -> ../init.d/netcheckd /etc/rc3.d/S20netcheckd -> ../init.d/netcheckd /etc/rc4.d/S20netcheckd -> ../init.d/netcheckd /etc/rc5.d/S20netcheckd -> ../init.d/netcheckd >
Точный механизм варьируется. В Fedora и Red Hat для того же используется chkconfig.
Да, и последнее... если вы захотите поэкспериментировать, делайте все на тестовой или виртуальной машине – в конце концов, мы же не хотим вывести из строя основной Linux-компьютер!
Файловые проверки
Файловые проверки очень часто используются при создании скриптов в Bash, и стоит запомнить эти, возвращающие значение true или false для конструкции if. Если вам такое не по силам, запомните хотя бы, где находится данный список!
- -b filename Специальный файл блочного устройства.
- -c filename Специальный файл символьного устройства.
- -d directoryname Проверка существования каталога.
- -e filename Проверка существования файла.
- -f filename Проверка существования обычного файла, не каталога.
- -G filename Проверка существования файла и принадлежности группе с текущим эффективным ID.
- -g filename True, если файл существует и установлен атрибут set-group-id.
- -k filename «Липкий» бит.
- -L filename Символьная ссылка.
- -O filename True, если файл существует и принадлежит пользователю с текущим эффективным ID.
- -r filename Проверка доступности файла на чтение.
- -S filename Проверка, является ли файл сокетом.
- -s filename Проверка, что файл имеет ненулевой размер.
- -u filename Проверка наличия бита set-user-id.
- -w filename Проверка доступности файла на запись.
- -x filename Является ли файл исполняемым.