Документация по ОС FreeBSD Суббота, 20.04.2024, 13:42
Приветствую Вас Гость | RSS
Меню сайта

Категории каталога
Shell [40]

Главная » Статьи » Программирование » Shell

Пример библиотеки для скриптов на языке bash - shell-framework [2010]
Вместо введения 

Я пишу скрипты на bash. Это скорее ближе к хобби, хотя иногда нужно и по работе. Конечно, не каждый день, но довольно часто и уже довольно давно. Бывают ситуации, когда либо не хочется пользоваться другими script-языками или когда у заказчика нет специалиста который сможет поддержать ваши скрипты в будущем, если они написаны, например, на perl - в общем, у меня бывало много случаев, когда моими скриптами должен был пользоваться не только я, но и еще кто-то и написаны они должны были быть на bash, а не на чем-то ином. Проанализировав требования к command line interface (CLI) программе, я систематизировал их и написал библиотеку, о которой и пойдет речь ниже.

Что я ожидаю?

Итак, что же мне нужно от CLI программ?
  1. Параметры. CLI программа должна уметь работать с параметрами. Параметры могут быть короткими (-M) и длинными(--multi-volume) . Первые нужны непосредственно для ввода их в командной строке и должны быть краткими для скорости ввода, вторые предназначены для использования программы в других скриптах и потому должны быть минимально читаемыми.
  2. Ожидаемые параметры и поведение. На самом деле я знаю лишь один by default параметр - "-h" и его длинный синоним "--help". Скрипт, будучи запущен с таким параметром, должен распечатать минимальную справку о себе и своих параметрах и закончить выполнение. (Как бы было прекрасно, когда, запуская на удаленной и мало знакомой машине скрипт, я мог бы быть уверен, что всегда смогу получить минимальное описание... :) )
  3. Наличие конфигурационных файлов. Конечно, это не обязательно, но, иногда, крайне желательно, в особенности для скриптов с дюжиной и более возможных параметров.
Пожалуй - это три основных требования. Библиотека  shell-framework была написана чтобы помогать мне писать скрипты, удовлетворяющие таким требованиям.
Еще один Hello world...
Давайте набросаем следующий скрипт:

#!/bin/bash

function printHelp () {
    cat - << END_OF_HELP
It is test script which just print Hello, <name>.
Usage $0 [parameter] ...
Paramter:
    -n <name> - define the <name>. default value is "World"
    -h        - print the help and exit from script
END_OF_HELP
}

declare name="World"

while getopts ":n:h" Option
do
  case $Option in
    n ) name="${OPTARG}" ;;
    h ) printHelp ; exit 1 ;;
  esac
done

echo "Hello, ${name}!"

Это хороший скрипт и он вполне отвечает своим целям. Однако, что будет, если мы захотим добавить еще один параметр - например "-b|--bye" - если он определен, например ‹‹-b "See you"››, то скрипт должен писать, вместо "Hello", "See you"? Нам придется дописать printHelp, добавив в нее описание нового параметра, проинициализировать его default value - "Hello", использовать, вместо "getopt", "getopts" (пример), что несколько изменит цикл и добавить еще строку в case блок. Надо так же заметить, что большинство блоков этого скрипта, таких как "print help", цикл перебора параметров и инициализации переменных, в том или ином виде являются общими для огромного числа скриптов в сети и, на мой взгляд, просто напрашиваются на вынесение их в библиотечные функции.
Посмотрим, как можно решить эту задачу с помощью shell-framework. Для начала скачайте и распакуйте куда-нибудь последнюю версию (ее можно взять здесь). Теперь напишите следующий script

#!/bin/bash

shF_PATH_TO_LIB="./shell-framework/lib"
source ${shF_PATH_TO_LIB}/base

setDescription "Hello world is example script."

#addOption <name> [defaultValue] [shortForm] [longForm] [type] [shortDescription] [longDescription] [priority] [notForConfig]]
#   type can be shF_SIMPLE_OPTION or shF_OPTION_WITH_VALUE
addOption "name" "World" "-n" "--name" "${shF_OPTION_WITH_VALUE}" "you can define your name as ${shF_COMMON_PARAMETER_NAME}." "" "110"


if ! initConfig "$@"  ; then
    exit 1
fi

echo "Hello, ${name}!"

Рассмотрим подробнее некоторые его части:

shF_PATH_TO_LIB="./shell-framework/lib"
source ${shF_PATH_TO_LIB}/base

shF_PATH_TO_LIB определяет, где находится библиотека. Нужно обязательно правильно проинициализировать эту переменную, потому что ее используют все библиотечные скрипты, чтобы подгружать друг-друга. К сожалению, из-за особенностей bash мне пришлось зарезервировать префикс "shF_" для всех переменных этой библиотеки и, понятное дело, использовать его в ваших скриптах не рекомендуется, если вы хотите избежать коллизий.
Строка с source подключает файл base, который по сути является набором стандартных подключений - чтобы не писать каждый раз "подключить библиотеку help, config и тд", я сделал этот файл.

setDescription "Hello world is example script."

Определяет краткое описание нашего скрипта. Оно будет использовано потом при показе help-а

addOption "name" "World" "-n" "--name" "${shF_OPTION_WITH_VALUE}" "you can define your name as ${shF_COMMON_PARAMETER_NAME}." "" "110"

Добавляет новый параметр(option) со следующими атрибутами: 
  • переменная name, которая будет хранить его значение
  • значение по умолчанию: World
  • краткая форма -n
  • длинная --name
  • после него ожидается значение (${shF_OPTION_WITH_VALUE}),
  • описание: "you can define your name as ${shF_COMMON_PARAMETER_NAME}."
  • При распечатке в help-е список параметров будет отсортирован в соответствии с весом заданным при инициализации - 110.

if ! initConfig "$@"  ; then
    exit 1
fi

initConfig "$@" пытается "распарсить" строку параметров в соответствии с конфигурацией.
Давайте теперь посмотрим, как он будет работать:

$ ./helloWorld.sh
Hello, World!
$ ./helloWorld.sh -n Nick
Hello, Nick!
$ ./helloWorld.sh --name Nick
<current time="">: ERROR ./shell-framework/lib/opts.parseOpts:122 The value for option (--name) must be defined.

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

$ ./helloWorld.sh --name=Nick
Hello, Nick!

Ну хорошо, а где же help? Ведь наш первый скрипт к этому моменту уже умел распечатывать подсказку по самому себе. Ну что же - давайте попробуем:

$ ./helloWorld.sh -h 
Hello world is example script.
Usage: ./helloWorld.sh [option(s)] [command]
Options:
 -h|--help print this help
 --help-options print detailed description of options using
 -l <parameter>|--shF_logLevel=<parameter> define the current log level (<parameter>). You can use the following number - 0(shF_EVERYTHING), 
1(shF_DEBUG), 2(shF_INFO), 3(shF_HIGH), 4(shF_WARN), 5(shF_ERROR). 
Config name is shF_currentLogLevel. Default value is "2".
 --logFile=<parameter> define the <parameter> as log stream. You can use stdError, stdOut or file name. Config name is shF_currentLogFile. Default value is "stdError".
 -c <parameter>|--config=<parameter> define the <parameter> as property file which will be loaded.
 -p|--print-config print the current configuration.
 -n <parameter>|--name=<parameter> you can define your name as <parameter>. Config name is name. Default value is "World"

Т.е. у нас уже есть не только автогенерация help, но и множество дополнительных параметров(опций) - уровень логгинга, использование лог файла, поддержка конфигурационных файлов. Про логи, я пожалуй здесь рассказывать не буду, а вот работу конфигурационной системы вполне можно описать.
Когда мы создаем параметр, то мы задаем так же и имя переменной, которая будет проинициализирована значением этого параметра. Порядок инициализации переменной будет следующим:
  1. значение по умолчанию, если оно есть
  2. значение из конфигурационного файла(далее "конфиг"), если оно есть
  3. значение командной строки,  если оно есть
Так что если мы создадим файл config.txt с таким содержимым

name="Config"

то вполне можем его использовать:
$ ./helloWorld.sh
Hello, World!
$ ./helloWorld.sh --config=./config.txt
Hello, Config!
$ ./helloWorld.sh -c ./config.txt --name=Virens
Hello, Virens!
На самом деле скрипт и сам умеет генерировать свои конфиги

$ ./helloWorld.sh -p
#define the current log level (). You can use the following number - 0(shF_EVERYTHING), 1(shF_DEBUG), 2(shF_INFO), 3(shF_HIGH), 4(shF_WARN), 5(shF_ERROR). Default value is "2".
#shF_currentLogLevel="2"

#define the  as log stream. You can use stdError, stdOut or file name. Default value is "stdError".
#shF_currentLogFile="stdError"

#you can define your name as . Default value is "World".
#name="World"

Вам достаточно просто перенаправить этот поток в файл, чтобы получить новый конфиг. Скрипт генерит конфиг с комментариями перед каждой переменной (они начинаются со знака #), которые вы можете сами определить при создании нового параметра (если не были заданы, то будет использована та же строка, что и для help). Как вы видите, все строки в этом config файле закомментированы - это произошло потому, что ни одна из переменных не отличалась на момент генерации конфига  от своего значения по умолчанию. Можно определить параметры и в этом случае конфигурация будет несколько иной:

$ ./helloWorld.sh -p -n Olga
#define the current log level (). You can use the following number - 0(shF_EVERYTHING), 1(shF_DEBUG), 2(shF_INFO), 3(shF_HIGH), 4(shF_WARN), 5(shF_ERROR). Default value is "2".
#shF_currentLogLevel="2"

#define the  as log stream. You can use stdError, stdOut or file name. Default value is "stdError".
#shF_currentLogFile="stdError"

#you can define your name as . Default value is "World".
name="Olga"

Таким образом, вы можете записать текущие настройки в файл и использовать их в будущем.
Как же решается задача с новым параметром "-b|--bye" описанная ранее? Довольно просто - добавлением строки с новым параметром:

#!/bin/bash

shF_PATH_TO_LIB="./shell-framework/lib"
source ${shF_PATH_TO_LIB}/base

setDescription "Hello world is example script."

#addOption <name> [defaultValue] [shortForm] [longForm] [type] [shortDescription] [longDescription] [priority] [notForConfig]]
#   type can be shF_SIMPLE_OPTION or shF_OPTION_WITH_VALUE
addOption "name" "World" "-n" "--name" "${shF_OPTION_WITH_VALUE}" "you can define your name as ${shF_COMMON_PARAMETER_NAME}." "" "110"
addOption "greeting" "Hello" "-b" "--bye" "${shF_OPTION_WITH_VALUE}" "you can define greeting word as ${shF_COMMON_PARAMETER_NAME}." "" "120"

if ! initConfig "$@"  ; then
        exit 1
    fi

echo "${greeting}, ${name}!"

$ ./helloWorld.sh -b "That's all" -n "folks"
That's all, folks!

Заключение

Эта библиотека не ограничивается работой с параметрами коммандной строки, конфигурационными файлами и автоматической генерации описания вашего скрипта. В ней так же есть ассоциативные массивы (из не было в bash до версии 4), некоторое расширение работы с trap и unit тестирование. Если будет желание, я расскажу как работать и с этими разделами.
Конечно же у нее есть недостатки - например скорость работы или сложность кода - все же bash плохо приспособлен для написания таких объемных скриптов. Но, несмотря на это, я ее уже успешно использую и буду рад, если кто-то найдет ее полезной.


Источник: http://beggytech.blogspot.com/2010/07/shell-framework.html
Категория: Shell | Добавил: oleg (19.09.2010) | Автор: Beggy
Просмотров: 976 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа

Beastie

Друзья сайта

Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
links

Copyright MyCorp © 2024