СЕРГЕЙ СУПРУНОВ
Конструктивный Dialog
Думаю, вам приходилось сталкиваться с работой утилиты dialog, входящей в состав большинства UNIX-систем (включая FreeBSD и практически все дистрибутивы Linux). Для Linux она часто используется в конфигурационных сценариях, во FreeBSD ее можно встретить при установке некоторых приложений из коллекции портов (например, oops, php). Она позволяет создавать простейшие диалоговые окна для взаимодействия с пользователем, используя псевдографику (как в sysinstall). Конечно, для «ортодоксального» системного администратора любое отступление от командной строки – кощунство, однако интерактивность в ряде случаев может быть весьма полезной.
Нужно заметить, что версии этой утилиты для Linux и для FreeBSD несколько отличаются. Данная статья будет посвящена версии, входящей в состав FreeBSD по умолчанию (конкретно – FreeBSD 5.3), а в завершение я скажу несколько слов об особенностях dialog в Linux.
Если в командной строке набрать «dialog» без параметров, то будет выведена справка по использованию утилиты. Нужно заметить, что богатством функций она не блещет. Так, с ее помощью можно задать пользователю вопрос, требующий ответа «Да» или «Нет», отобразить содержимое файла или результат работы какой-нибудь команды, запросить ввод строки, предложить выбрать одну или несколько альтернатив из списка, создать меню, отобразить список некоторых значений в виде дерева. Dialog не позволяет комбинировать несколько элементов в одном окне (например, группу полей ввода), то есть если вы решите использовать его для разработки сценария настройки сетевых интерфейсов, то такие параметры, как IP-адрес, маска, шлюз по умолчанию, придется запрашивать поочередно в отдельных окнах. Тем не менее на базе этой утилиты можно строить достаточно «продвинутые» сценарии.
Синтаксис команды, с вашего позволения, дублировать не буду. Для разминки сразу напишем сценарий, позволяющий управлять сервером Apache – запускать его, останавливать, выполнять «мягкий» и «жесткий» перезапуск (graceful и restart соответственно). Обычно для этого используется утилита apachectl, однако если Apache работает достаточно стабильно, то ее ключи начинают забываться. Следующий пример демонстрирует решение поставленной задачи:
Пример 1: apctl.sh – Сценарий управления сервером Apache
#!/bin/sh
# apctl.sh
dialog --title "Apachectl interface" \
--menu "
Данный сценарий управляет сервером Apache.
Выберите действие из предложенных ниже:" 17 50 8 \
start "Запуск сервера Apache" \
stop "Останов сервера Apache" \
restart "'Жесткий' перезапуск" \
graceful "'Мягкий' перезапуск" \
status "Статус сервера" \
fullstatus "Подробный статус" \
configtest "Тест конфигурационного файла" \
help "Вывод справки" 2> apctl.tmp
COMMAND=`cat apctl.tmp`
COMMAND="/usr/local/sbin/apachectl $COMMAND"
dialog --title "Apachectl interface" \
--yesno "
ВНИМАНИЕ! Будет выполнена следующая команда:
$COMMAND
Продолжить?" 10 50
if [ $? = 0 ]; then
$COMMAND
fi
rm apctl.tmp
Рассмотрим подробнее, что происходит. Основная работа выполняется утилитой dialog, которой передаются параметры для построения в данном случае диалога «Меню», позволяющего выбрать один из вариантов среди предложенных. Синтаксис достаточно прост. Ключ --title задает заголовок окна, далее ключ --menu указывает тип диалога и требует следующих параметров:
- Сопроводительный текст.
- Высота окна (в символах).
- Ширина окна (в символах).
- Количество пунктов меню.
- Пары «Ключ» – «Пояснение» для каждого пункта меню.
В результате мы получим окно, изображенное на рис. 1.
Рисунок 1
Ключ выбранного пункта меню выводится в стандартный поток сообщений об ошибках (stderr), и, чтобы можно было его в дальнейшем использовать, мы перенаправляем strerr (имеющий дескриптор 2) в файл с помощью такой конструкции: 2> apctl.tmp. В дальнейшем сохраняем содержимое этого файла в переменной $COMMAND и дополняем полным именем утилиты apachectl. Таким образом, в переменной $COMMAND оказывается команда, которую нужно исполнить. Теперь уточним у пользователя, действительно ли он хочет выполнить эту команду, для чего используем еще один диалог типа «Да/Нет». Результат взаимодействия с пользователем в данном случае можно получить из переменной $?, хранящей код возврата последней команды. Если была выбрана кнопка «Yes», код возврата будет 0 (успешное завершение).
Ну и прежде чем завершить работу, сценарий apctl.sh должен удалить созданный им временный файл apctl.tmp. Учитывая, что этот файл создается в том же каталоге, из которого был вызван сценарий, вероятность, что несколько пользователей запустят его одновременно в одной и той же папке, достаточно низка. Тем не менее если она все же существует, то имеет смысл имя временного файла формировать динамически, как будет показано ниже.
Перейдем к более серьезному примеру: напишем сценарий, позволяющий формировать файл .htaccess для управления поведением сервера Apache в конкретных каталогах. Как вы сейчас увидите, работа эта неблагодарная, и создать файл тонкой настройки вручную намного проще. Тем не менее если вы выделяете своим пользователям место под домашнюю страничку и предоставляете им терминальный доступ к домашнему каталогу, то подобным сценарием можно несколько упростить жизнь не только хозяевам странички, но и службе технической поддержки. К тому же в данном примере мы встретимся почти со всеми типами диалоговых окон, что очень хорошо для усвоения материала.
Чтобы не завязнуть в возможностях .htaccess и не уйти от основной темы статьи, ограничимся двумя простейшими опциями: обработкой ошибки 404 «Файл не найден» (опция ErrorDocument 404) и включением SSI-обработки для файлов с определенными расширениями (опция AddHandler server-parsed).
Опция ErrorDocument может принимать в качестве второго параметра (первый в нашем случае равен «404» – номер обрабатываемой ошибки) либо просто текстовую строку, которая будет использоваться в стандартной странице ошибки, либо URL файла, возвращаемого пользователю при запросе несуществующей страницы. Причем файл может располагаться как локально на этом же сервере, так и на другой, удаленной машине. В первом случае указывается относительный или абсолютный (от корня веб-сервера) путь к файлу, во втором – полный URL ресурса, включая протокол:
ErrorDocument 404 “Запрошенная страница не существует
ErrorDocument 404 /_srv_/error404.html
ErrorDocument 404 http://my.server.ru/errors/error404.htm
С опцией AddHandler все еще проще – она первым параметром принимает тип обработчика (в нашем случае это server-parsed, говорящий, что будет обрабатываться SSI), а далее – список расширений файлов, которые должны подпадать под действие этого обработчика:
AddHandler server-parsed .shtml .shtm
Как обычно, сначала приведу весь сценарий полностью:
Пример 2: mkhtac.sh – Сценарий для создания .htaccess
#!/bin/sh
# mkhtac.sh
# Создание вспомогательных файлов
TF=`mktemp –t dlg`
TF_TREE=`mktemp –t tree`
# Запись в файл дерева каталогов
find www -type d > $TF_TREE
# Регистрация обработчика сигналов
trap "rm -f $TF $TF_TREE" 0 2 9 15
#==[Dialog 0]==
dialog --title "Make .htaccess: Confirmation" \
--yesno "
Данный сценарий поможет вам
создать файл .htaccess.
Продолжить?" 10 40
if [ $? != 0 ]; then
exit
fi
#==[Dialog 1]==
dialog --title "Make .htaccess: Select dir" \
--ftree $TF_TREE "/" "
Укажите каталог, в котором должен
быть создан файл .htaccess:" 19 40 10 2> $TF
if [ $? != 0 ]; then
exit
fi
HTDIR=`cat $TF`
#==[Dialog 2]==
dialog --title "Make .htaccess: Select options" \
--checklist "
Укажите опции, которые следует добавить
в формируемый .htaccess:" 15 50 2 \
ErrorDocument "Обработка ошибки 404" OFF \
AddHandler "Включение SSI" OFF 2> $TF
if [ $? != 0 ]; then
exit
fi
ERROR=`grep "ErrorDocument" $TF`
SSI=`grep "AddHandler" $TF`
if [ "$ERROR" != "" ]; then
#==[Dialog 3a]==
dialog --title "Make .htaccess: ErrorDocument" \
--radiolist "
Выберите один из способов обработки ошибки 404:" 15 60 3 \
MSG "Текстовое сообщение об ошибке" ON \
FILE "Перенаправление на локальный файл" OFF \
REDIR "Перенаправление на удаленный ресурс" OFF 2> $TF
ERR_MSG=`grep "MSG" $TF`
ERR_FILE=`grep "FILE" $TF`
ERR_REDIR=`grep "REDIR" $TF`
QUOT=""
if [ "$ERR_MSG" != "" ]; then
QUOT="\""
TEXT="
Введите текст сообщения об ошибке 404:"
fi
if [ "$ERR_FILE" != "" ]; then
TEXT="
Введите абсолютное имя файла (от корня веб-сервера)
или имя относительно текущего каталога:
Пример: /errors/err404.html"
fi
if [ "$ERR_REDIR" != "" ]; then
TEXT="
Введите полный URL ресурса (включая наименование протокола):
Пример: http://my.server.ru/errors/err404.html"
fi
#==[Dialog 3b]==
dialog --title "Make .htaccess: ErrorDocument" \
--inputbox "$TEXT" 10 70 2> $TF
ERRMSG=`cat $TF`
ERR2HT="ErrorDocument 404 $QUOT$ERRMSG"
echo $ERR2HT
fi
if [ "$SSI" != "" ]; then
#==[Dialog 4a]==
dialog --title "Make .htaccess: SSI parse" \
--checklist "
Отметьте, какие файлы должны
обрабатываться парсером SSI:" 15 40 4 \
".shtml" "файлы shtml" ON \
".shtm" "файлы shtm" ON \
".html" "файлы html" OFF \
".htm" "файлы htm" OFF 2> $TF
EXTLST=`cat $TF | tr "\"" "\0"`
SSI2HT="AddHandler server-parsed $EXTLST"
echo $SSI2HT
fi
# Запись результатов в файл .htaccess
HTFILE=$HTDIR/.htaccess
touch $HTFILE
[ "$ERR2HT" != "" ] && echo $ERR2HT >> $HTFILE
[ "$SSI2HT" != "" ] && echo $SSI2HT >> $HTFILE
#==========================[Dialog 5]==
dialog --title "Make .htaccess: Finish" \
--msgbox "
Работа завершена. Сейчас созданный файл
будет выведен на экран, чтобы вы могли
ознакомиться с результатом.
Дальнейшие изменения в созданный файл
можно будет выполнить вручную." 15 50
#==[Dialog 6]==
dialog --title "Make .htaccess: Result" \
--textbox $HTFILE 15 50
В первых строках сценарий подготавливает два временных файла. Они создаются утилитой mktemp с префиксами dlg и tree соответственно и по умолчанию будут размещены в каталоге /tmp.
Первый из них ($TF) служит для перехвата вывода утилиты dialog, во второй ($TF_TREE) заносится результат работы команды find, которая возвращает имена всех каталогов, найденных в папке www. Благодаря тому, что mktemp создает файлы со случайными именами, можно не беспокоиться о том, что скрипт, запущенный одним пользователем, удалит временные файлы, с которыми в этот же момент времени работает другой экземпляр сценария.
Командой trap регистрируем обработчик для сигналов 0 (нормальное завершение), 2 (SIGINT – прерывание, например, по Ctrl-C), 9 (SIGKILL – безусловное завершение) и 15 (SIGTERM – программное прерывание). При поступлении одного из этих сигналов будет выполнена команда rm, удаляющая созданные сценарием временные файлы.
Фрагмент сценария, помеченный как [Dialog 0], выводит окно типа «Да/Нет» с вопросом о продолжении, и при ответе «Нет» работа сценария завершается.
[Dialog 1] выводит дерево каталогов, корнем которого является в нашем случае папка www, и пользователь может выбрать в нем каталог, в котором следует создать файл .htaccess. Результат представлен на рис. 2, выбор пользователя заносится в переменную $HTDIR.
Рисунок 2
[Dialog 2] (см. рис. 3) выводит список флажков (checklist), предлагая пользователю отметить те опции, которые он желает иметь в создаваемом файле .htaccess. Результат выбора раскладывается по переменным $ERROR и $SSI с помощью утилиты grep. Если та или иная опция не упоминается во временном файле, то соответствующая переменная останется пустой, что и используется при определении последующих действий.
Рисунок 3
Так, если была выбрана опция ErrorDocument, то на экране появится диалоговое окно, изображенное на рис. 4. За его формирование отвечает вызов dialog, помеченный, как [Dialog 3a]. Здесь реализуется список зависимых кнопок (radiolist), позволяющий выбрать один из предложенных вариантов. В зависимости от выбора пользователя в переменную $TEXT заносится сообщение, которое в дальнейшем используется в следующем диалоге ([Dialog 3b]), поясняя пользователю, что же он должен ввести.
Рисунок 4
Значение, введенное в [Dialog 3b] (рис. 5), заносится в переменную $ERRMSG, и вместе с именем самой опции в переменной $ERR2HT формируется строка, которая в дальнейшем будет записана в файл.
Рисунок 5
Следующий диалог ([Dialog 4a]) предлагает пользователю отметить типы файлов, которые будут обрабатываться SSI-парсером (рис. 6). Поскольку отмеченные пункты при выводе в stderr (перенаправляемый в файл) заключаются в кавычки, то при их извлечении кавычки нужно удалить, для чего используется утилита tr.
Рисунок 6
После всех этих мучений наконец-то создается файл .htaccess, и в него записываются сформированные строки (если они не пустые). [Dialog 5] поздравляет пользователя с этим знаменательным моментом (рис. 7), а [Dialog 6] выводит на экран полученный результат, демонстрируя работу диалога типа «textbox», который выводит содержимое указанного в качестве параметра файла.
Рисунок 7
Вернемся к свойствам утилиты dialog. Цветовую гамму окон и некоторые другие параметры (например, отображение тени) можно изменить. Для этого используется конфигурационный файл, шаблон которого можно создать такой командой:
$ dialog --create-rc
В результате в файл будет записана конфигурация по умолчанию. Полученный файл следует либо переименовать в .dialogrc и поместить в домашнюю папку пользователя (в результате он будет распространяться на все диалоги, запущенные этим пользователем), либо указать к нему путь в переменной окружения DIALOGRC. Редактирование конфигурационного файла сложностей вызвать не должно, поскольку каждая строчка в нем достаточно хорошо прокомментирована.
Рисунок 8
Ну и как было обещано – несколько слов об особенностях dialog в Linux. Так как «стационарного» Linux у меня под рукой нет, я воспользовался LiveCD-дистрибутивом Knoppix. В отличие от FreeBSD-версии в Linux dialog имеет ряд дополнительных опций и возможностей. Так, с его помощью можно реализовать диалоги выбора даты (--calendar, рис. 8), выбора файла (--fselect, рис. 9), вывести индикатор выполнения процесса, именуемый в народе «термометром» (--gauge), запросить ввод пароля (элемент --passwordbox), можно добавлять в диалоговые окна дополнительные кнопки и т. д. Зато в нем нет «деревьев» (элементы --tree и --ftree в FreeBSD-версии), так что рассмотренный нами пример 2 на Linux работать не будет, и придется запрашивать папку для создания .htaccess другим способом (например, с помощью поля ввода).
Рисунок 9
Ряд параметров (цвет элементов окна, наличие тени и т. д.) в Linux-версии можно изменить не только в конфигурационном файле, но и в командной строке с помощью соответствующих ключей.
Кстати говоря, Linux-версию утилиты dialog можно установить и на FreeBSD. Она доступна в коллекции портов под именем cdialog (/usr/ports/devel/cdialog).
Помимо консольной версии существует Xdialog, выполняющий аналогичные функции в графической среде. В том же Knoppix его работу можно наблюдать, например, при вызове сценария настройки сети (рис. 10). А на рис. 11 еще раз показан «Календарь», но уже в исполнении Xdialog.
Рисунок 10
Рисунок 11
В завершение статьи отмечу также, что библиотеку, лежащую в основе утилиты dialog, можно использовать и непосредственно в своих программах на C/C++. Существуют также интерфейсы к данной библиотеке для Perl, Python и Ruby (их можно установить из коллекции портов: devel/p5-Dialog, devel/py24-dialog и devel/ruby-dialogs соответственно). Дополнительную информацию, как обычно, можно получить на страницах справочного руководства man dialog(1) и dialog(3).