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

Это гостевой пост автора Beggy. Оригинальный пост доступен по этой ссылке.

О чем все это?
Библиотека shell-framework - это набор функций, которые вы можете вызвать из своего скрипта. Она была задумана для облегчения создания скриптов на языке bash и полностью написана на этом языке. В основном библиотека писалась для упрощения работы с опциями командной строки, генерации краткой справки(help-а) скрипта и работы с конфигурационными файлами. В этой статье я постараюсь показать как именно можно упростить написание рутинного кода, связанного с этими задачами, используя shell-framework.



Установка
Процесс установки shell-framework предельно прост. Откройте страничку download проекта и скачайте последнюю версию архива с библиотекой, например, shell-framework_0.22.tar.bz2. Предположим, вы положили ее в текущий каталог, который специально создали для экспериментов. Теперь нужно распаковать библиотеку - например так:

$ tar xjf ./framework_0.21.tar.bz2
после чего должен появиться подкаталог shell-framework. Скачанный архив вам больше не нужен и его можно удалить.

Пример использования библиотеки для написания скриптов
Давайте напишем простейший helloWorld скрипт в том самом экспериментальном каталоге и на его примере разберем основные возможности библиотеки:
#!/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}!"
Вы можете скопировать скрипт прямо отсюда в файл. Я назвал у себя этот файл helloWorld.sh и дальше буду ссылаться на него именно под этим именем. Не забудьте сделать его исполняемым - например так:
$ chmod +x ./helloWorld.sh
Рассмотрим отдельные части более подробно:
#!/bin/bash
shF_PATH_TO_LIB="./shell-framework/lib"
source "${shF_PATH_TO_LIB}/base"
Библиотека shell-framework - это набор bash скриптов, каждый из который, в свою очередь, определяет набор функций. Таким образом, чтобы начать использовать их в скрипте нужно просто подключить нужный файл с помощью инструкции source или ".".Однако, библиотека состоит из нескольких файлов и они тоже должны, в свою очередь, подключить необходимые функции (например функциям работы с опциями командной строки нужны функции работы с ассоциативными массивами). Для этого определяется переменная shF_PATH_TO_LIB, в которой прописывается, где лежат файлы shell-framework/lib, и именно она используется внутри библиотеки, чтобы собрать весь необходимый нам инструментарий. Ее определение является обязательным и оно должно предшествовать source.
setDescription "Hello world is example script."
setDescription - первая функция shell-framework, которую мы использовали. Она устанавливает краткое описание нашего скрипта. Позже эта строка будет использована при генерации справки (help-а).
#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, которая добавляет новый параметр (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 "$@" пытается "распарсить" строку параметров в соответствии с конфигурацией, заданной выше. Если произойдет ошибка, скрипт прервет свое выполнение. При выполнении initConfig, среди прочего будет создана переменная name со значением по умолчанию (World), потом опции командной строки будут перебраны и если найдется комбинация -n <value> или --name=<value>, то name получит новое значение (<value>).
echo "Hello, ${name}!"
Используем значение переменной name и печатаем приветствие.Итак, мы задействовали 3 функции из библиотеки (setDescription, addOption, initConfig) и набросали тестовый скрипт. Давайте теперь посмотрим, что же он в результате умеет?

Использование скрипта helloWorld.sh
Попробуем запустить скрипт, который мы только что написали, и убедимся, что мы можем передать ему значение для переменной name, как опцию командной строки.
$ ./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) ожидается значение. Дело в том, что shell-framework предполагает, что при использовании "длинных" option будет использована форма --<option name>=<option value>:
$ ./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"
Как видите, скрипт оказывается содержит намного больше чем единственная option, которую мы с вами сначала определили. Причиной тому - использование библиотеки base, которая на самом деле написана, чтобы избежать рутинного перечисления стандартных, на мой взгляд options. Если в вашем случае это не так, то можно обойтись и без base, подключая по отдельности остальные файлы библиотеки.Наша с вами option name, согласно весу 110, располагается в конце этого списка. Над ней две другие option позволяют нам использовать еще одну возможность библиотеки shell-framework - конфигурационные файлы. Давайте рассмотрим их подробнее.Когда мы создаем option, то мы задаем так же и имя переменной. Порядок инициализации этой переменной будет следующим:
  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!
Option -p используется для генерации конфигурационного файла, что может быть удобно, если вы не хотите создавать его с нуля или просто для записи какого-нибудь удачного, но большого набора опций.
$ ./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"
Таким образом, конфигурационные файлы, которые умеет использовать наш скрипт - это обыкновенный текст, в котором значок # начинает комментарии, а пары имя-значение используются для определения значений конкретных переменных.

Заключение
Вы безусловно успели заметить, что я не описал options связанные с logging-ом скриптов(см. help выше). Это не единственная из неописанных возможностей библиотеки shell-framework - среди них так есть работа с ассоциативными массивами (да - они есть в bash 4, но в более ранних версиях, увы, этот тип данных отсутсвует), некоторое расширение, позволяющее вешать на trap не одну функцию, а несколько, использование unit тестирования и тд.

22 комментариев: |высказаться!| RSS-лента дискуссии.|
Roman Cheplyaka комментирует...

"source ${shF_PATH_TO_LIB}/opts" -- хоть переменные в кавычки возьмите...

В целом, от беглого просмотра кода осталось жуткое ощущение.

Beggy комментирует...

Да, конечно, кавычки - это правильно. Обычно я это делаю, но здесь почему-то опустил. Исправил.
"Жуткое ощущение" - это определено не то, на что рассчитывают "писатели кода" :) Будет ли уместным спросить Ваши рекомендации или замечания? Если не хотите здесь, то можно обсудить это в почте.

virens комментирует...

@Roman Cheplyaka комментирует...
В целом, от беглого просмотра кода осталось жуткое ощущение.
Я за то, чтобы решение работало. Первым делом - самолёты, а красивости потом. У меня сия библиотека работает, поэтому, собственно, решили запостить тут.

И да, хочется больше подробностей.


@Beggy комментирует...
Если не хотите здесь, то можно обсудить это в почте.
Николай, выбрось эту дурную привычку - обсуждение того, что касается поста, идёт в комментах поста, и только после того, как обсуждение выходит за все разумные рамки, оно может перетечь в письма.

Кстати, если кто заметил, это первый гостевой пост. Может быть, блин получился несколько комом, но зато я получил ценный опыт в этом деле.

zysyl комментирует...

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

Unknown комментирует...

@Roman Cheplyaka: Отчего у вас жуткое впечатление осталось?

Лично на мой взгляд код написан очень хорошо. Хоть и местами его тяжеловато читать из-за обилия переменных, как мне кажется. Но отлично написанные комментарии помогает понять сам код, а осмысленные имена переменных и функций, так же облегчают чтение и понимание кода.

Единственный момент, где я на некоторе время встал в ступор, это строки 136 и 137, файла logging, из-за открывающей скобки в case ... esac. Но заглянув в man bash сразу все встало на свои места (я не знал, что в bash так тоже можно).


@Beggy: Спасибо Вам за труд! Очень хороший фрейворк :)

virens комментирует...

Присоединяюсь к теме лицензии - что-то на сайте её не видно. Надо бы исправить.

Кроме того, реквестирую deb-пакет, так как установка в том виде, в котором она есть сейчас, не есть хорошо.

zysyl комментирует...

насколько я понял help каждый раз генерируется заново это так? Лично моё мнение help у законченного скрипта, это статические данные и каждый раз их генерировать это трата ресурсов и времени 0m2.132s было бы прикольно добавить стандартный ключ генерации хелпа и запись этого хелпа в какой-то стандартный файл, а потом просто чтение из файла этих данных. Второе моё имхо, может библиотеки лучше переписать на перле или питоне, мне кажется что при этом возрастет производительность.

Beggy комментирует...

@zysyl
Help как внешний файл - это возможно неплохая идея, но есть несколько контр возражений: 1) Основная идея этого фреймворка - держать все данные, определения опций и описаний в одном месте. Введение дополнительного файла несколько нарушает эту схему. 2) файл help могут забыть перегенерировать или вообще упустить при переносе скрипта. 3) help используется не настолько часто и время выполнения, на мой взгляд, здесь несущественно.
Задумка этой библиотеки возникла лет 6 назад, когда я написал несколько администрирующих скриптов на perl-е. Я тогда написал нечто подобное и конечно же это работало намного быстрее. :) Сейчас это решение для bash и на bash. Не стану лукавить и отрицать, что при написании существовал и спортивный интерес: "Можно ли это вообще сделать на данном языке?" Но вопрос быстродействия меня никогда не волновал - мне кажется, что если вам нужна скорость, то bash всегда будет в числе последних претендентов. С другой стороны - как вы себе представляете инициализацию bash переменных из perl скрипта? Или речь идет о комбинированное решение - медленные части переписать на perl, а сверху их обернуть bash? - Такого монстра будет нелегко поддерживать. Не говоря уже и о том, что иногда ни perl, ни python на сервере может и не быть.
Но возможно я с самого начала пошел не потому пути рассуждений читая ваше имхо - может вы имели в виду, что нужно полностью написать аналог такой библиотеки для perl? Ну эта задача вполне решаема, но видимо не в ближайшее время.
Что касается скорости, то я планирую в самое ближайшее время написать модуль, который будет использовать возможность bash 4 работать с ассоциативными массивами. Сейчас они эмулируются и работа с такими структурами данных занимает значительное время.

Beggy комментирует...

@Angel2S2:
Как я уже говорил - вначале стоял вопрос можно ли вообще нечто подобное написать на bash. И пока я писал этот framework я узнал много нового про bash. :)) Кусок, про который вы упоминаете, поразил меня чуть менее того факта, что на bash можно сгененрировать stack trace :))

За framework - не за что. Мне и вправду было интересно его написать и старался это сделать хорошо. Пожалуйста, высказывайтесь что именно вы хотели бы видеть в нем. В ближайшее время я опубликую продолжение этой статьи уже с более конкретным описанием каждой из библиотек, здесь или у себя в блоге.

Beggy комментирует...

Лицензия... Ох - о ней я как-то и не думал до сих пор... Полагаю это будет GPL или что-то на нее похожее. Какая лицензия наиболее свободна? Может ли меня кто-нибудь проконсультировать по этому вопросу?

@virens
Deb-пакет. Ого куда замахнулись :)) Ну давай попробуем. А куда его выложить? Есть ли репозитории таких вещей?

zysyl комментирует...

Или речь идет о комбинированном решение - медленные части переписать на perl, а сверху их обернуть bash? - вот это я и имел в виду. Пакеты, наверное на вашем сайте выложить, хотя я не доганяю зачем генерировать пакеты, разве что ради того что-бы все было по феншую дистрибутива. Статью можно частично или полностью распространять, канечно с указанием на оригинал? Будем ждать продолжения статьи :)

zysyl комментирует...

Насчёт лицензии мне кажется это или Apache License v2.0 или GPL3.

Beggy комментирует...

Лицензия определена - GPLv3. Все необходимые изменения внесены в сорцы и новый релиз (0.23) выложен на сайт.
@zysyl: Эту статью, как и статьи на моем блоге можно свободно копировать с указанием источника.
Что касается пакета, то мне кажется этой правильной идеей, так как в Linux-ах существует стандартная модель распространения программ и стоило бы ей следовать. Так что для меня вопрос не "зачем", но "как". Выложить на origo не совсем верная мысль, потому что там нет debian репозитория. В общем ждем ответа гуру :)

Unknown комментирует...

@Beggy: Для Debian подобных дистрибутивов можно использовать https://launchpad.net/. Очень многие именно так и делают. А вот для rpm, ebuild и т.п. уже подсказать не могу. Т.ж. я не видел серверов, где есть репозитории сразу для всех или хотя бы нескольких систем.

virens комментирует...

@zysyl комментирует...
комбинированном решение - медленные части переписать на perl, а сверху их обернуть bash?
Я бы вот за это бил бы по рукам длинной стальной линейкой: если уж изобретать велосипед, то из железа целиком, а не куски из стали, части из алюминия со вставками и соплей с пластилином.

Пример (печальный) из моей тяжёлой жизни: есть симулятор адаптивной оптики (SciAO), сделанный на на угрёбищном SciLab - графика силаба такая жуткая, что лучше б её вообще не было. Однако на силабе только обёртка для: библиотеки Фурье-преобразований на Сях, костылястой утилиты ray-tracing на плюсах, куски утилит работы с матрицами на фортране. Всё очень старое, собирается только у автора (который в довершение ко всему ещё и виндузятник, т.е. собирал весь этот балаган под cygwin). Автор свалил из области и забил на всё. У меня другой фразы для всего этого кроме "кг/ам", простите за мой французский, просто нет.

Так что костыли только из одного материала. Пожалуйста. :-)

Статью можно частично или полностью распространять, канечно с указанием на оригинал?
Лицензия в подвале блога. Она там совсем не зря :-)

@Beggy комментирует...
Что касается пакета, то мне кажется этой правильной идеей
Я в общем был несколько удивлён, что пакета нету. Как бы не все на слаквари...

Так что для меня вопрос не "зачем", но "как".
Рядом с сырцами выложить ссылку на Deb-пакет. Зарегистрироваться на каком-нибудь ресурсе хоть google.code что ли. Там всё и сразу, в одном флаконе - и примитивный багтрекинг, и репозиторий, и downloads.

В общем ждем ответа гуру :)
Не знаю, какого вы гуру ждёте, но я бы не стал изобретать велосипедов и пошёл бы на сорсфорж. Да, его новый интерфейс, конечно, на любителя. Но всё-таки. Или ГуглКод как вариант.

Анонимный комментирует...

ИМХО, если и делать данный фреймворк, то он должен работать и при таком начале файла '#!/bin/sh'. Т.к. не везде BASH используется, а стандартный shell есть на всех системах.

Unknown комментирует...

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

Beggy комментирует...

@Анонимный:
Несомненно Вы правы. Но во-первых я использовал некоторые специфичные bash вещи, а во-вторых на большинстве современных серверов он есть. Так что я думаю, что это допустимый компромисс.

Beggy комментирует...

После некоторых раздумий и действий был создан репозитарий и пакет shell-framework в нем. Шаги установке репозитория и пакет описаны здесь

Unknown комментирует...

Отличный фреймворк.
Очень хотелось бы видеть deb-пакет с фреймворком в официальном репозитории Debian/Ubuntu.

Beggy комментирует...

Спасибо, Константин. Мне правда кажется, что framework довольно сырой еще, чтобы замахиваться на официальный репозиторий :)

Maratich комментирует...

С Новым Годом :)
по поводу аналогов launchpad.net для rpm - https://build.opensuse.org/
сборка для нескольких дистрибутивов описана тут -
http://en.opensuse.org/Build_Service/cross_distribution_package_how_to

Отправить комментарий

Подписаться на RSS-ленту комментариев к этому посту.