Заработай на задачках

Оригинал: Working with Command Arguments by Dave Taylor

Перевод: собственный, вольный с исправлениями

В этой статье будет затронута одна из основных особенностей создания shell-скриптов: обработка аргументов командной строки. Большинство простых сценариев не имеют входных параметров, а по мере развития обзаводятся одним или двумя. Если их становится много, то для обработки аргументов используется getopt. Увеличение сложности может привести и к переписыванию на языках высокого уровня, к примеру, С++ или Ruby.

Самый простой способ обработки аргументов командной строки – установка флага с помощью условного оператора:

if [ "$1" = "-a" ]; then flag_a=1 fi

При реализации такого подхода возникает несколько проблем. Одна из них – захламление исходного кода дополнительными конструкциями. Так, перед этим примером необходимо предварительно обнулить переменную с помощью выражения flag_a=0. Иначе нельзя точно определить, какое значение присвоит ей оболочка командной строки при инициализации.

Другая проблема состоит в том, что выполнение этого блока никак не влияет на параметры командной строки: $1 по-прежнему может быть флагом (-a), другим аргументом или значением, введённым пользователем. Следовательно необходимо будет добавить дополнительные проверки при считывании остальных опций.

Обработка нескольких аргументов командной строки в shell-скрипте

Для наглядности представим, что существует простой скрипт. Он работает в качестве обёртки к чему-то на подобии curl: если передать в него ссылку, то содержимое web-страницы будет скачано и сохранено в файле на локальном диске. К тому же, с помощью воображаемого флага -a можно увидеть ход работы.

Команда для выполнения сценария в bash будет выглядеть следующим образом:

getpage.sh -a http://ozi-blog.ru/

Аргументы командной строки инициализируются в том же порядке: $0 = getpage.sh, $1 = -a, $2 = http://ozi-blog.ru и их общее количество $# = 2.

Стоит помнить, что $# – количество всех аргументов, а не сумма слов в команде. Можно подумать, что если скрипт вызывается без каких-либо параметров, то $# должно равняться 1 (учитывая имя сценария), однако на самом деле $#=0.

Подобная особенность нумерации существует с зари развития UNIX и известна под названием “проблема нулевого индекса”. Так, в массивах первое значение можно получить по индексу 0 или array[0]. Для многих разработчиков это понятно, для других же – вызывает затруднение. Начинающие программисты могут даже игнорировать нулевой индекс в языке C и начинать использовать ячейки массива с 1, а не 0.

После успешной проверки на наличие флага -a для первого аргумента ($1) следует переместить все значения остальных параметров ($2) на одну позицию влево ($1=$2). Тогда в остальной части программы можно будет считать, что в $1 находится адрес ссылки. Отпадает необходимость избыточных проверок на наличие или отсутствие флага.

Это можно сделать с помощью команды сдвига (shift). В итоге получаем правильный способ обработки одного опционального аргумента командной строки в shell-скрипте:

flag_a=0 if [ "$1" = "-a" ]; then flag_a=1 shift fi url=$1

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

Решить эту задачу можно немного изменив предыдущий пример:

outputspecified=0 if [ "$1" = "-o" ]; then outputspecified=1 outputfilename="$2" shift 2 fi

Таким образом, команда shift принимает одно число. Оно указывает на сколько позиций необходимо сдвинуть значения аргументов командной строки. Это легко продемонстрировать, если вывести их до и после выполнения блока кода с shift 2:

$ sh getpage.sh -o test.html какая-то-ссылка $# = 3 $1 = -o $2 = test.html $3 = какая-то-ссылка ----- выполнение команды shift 2 ----- $# = 1 $1 = какая-то ссылка $2 = $3 =

Изначально все три аргумента заданы и их количество ($#) равно 3. После выполнения shift 2 все значения сместились на две позиции и их количество также уменьшилось.

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

Обработка большого количества аргументов командной строки в Bash

Даже обработка двух флагов сопряжена с трудностями. Качества кода выше значительно ухудшится, если аргументы могут указываться в произвольном порядке. Но даже без этого, для обработки трёх стартовых условий (-a, -c, -o) перед операцией сдвига понадобится проверка всех вариантов. Потом еще раз проверка и сдвиг. И еще раз проверка и сдвиг. Кроме того, пользователи любят комбинировать опции запуска программы между собой. Представьте, как ужасно разбирать на составные части входные данные вида -ac -o или -aco.

Введение в getopt

Если необходимо создать объёмный сценарий с большим набором опций запуска, то использование программы getopt – одно из лучших решений. С помощью этой утилиты можно легко разобрать на составные части различные флаги и значения, скомбинировать их, а затем обработать удобным способом в одном месте.

Алгоритм применения getopt выглядит следующим образом:

  1. Разбор существующих аргументов командной строки с помощью getopt. В результате будет создана временная переменная с отсортированными данными по заданному шаблону.
  2. Замена аргументов на новые командой set.
  3. Последовательная обработка полученных значений в цикле.

Наглядно продемонстрировать эту последовательность действий можно на простом примере. Предположим, что существует скрипт, ход выполнения которого задаётся тремя опциями: -s, -p и –t.

Вначале необходимо привести аргументы командной строки к структурированному виду командой getopt. Это поможет избавиться от влияния способа ввода данных пользователем:

args=$(getopt sp:t $*)

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

Затем можно проверить значение переменной $? – статус выполнения последней операции. Если при обработке утилитой getopt параметров возникла какая-то ошибка, то её значение будет отличаться от 0. В этом случае можно вывести подсказку по использованию программы и завершить выполнение:

if [ $? != 0 ] ; then echo \ "Использование: $(basename $0) {-p SFX} {-n} {-t}" echo " -s выводить число подходящих файлов" echo " -p использовать суффикс SFX для имён файлов" echo " -t режим тестирования – не выполнять реальных действий" exit 0 fi

На практике часто встречаются скрипты, в которых подобная подсказка заключена в отдельную функцию. Так исходный код лучше воспринимается. Следует отдельно отметить выражение $(basename $0) — оно отделяет имя запущенной программы от полного пути к исполняемому файлу.

После обработки ошибок продолжаем выполнять полезные действия:

set -- $args

Теперь все аргументы отсортированы и готовы к обработке. Производится она обычно с помощью конструкции case, заключённой в цикл. Выглядит это следующим образом:

i=0 for i do case "$i" in -s ) flag_s=1 ; shift ;; -p ) flag_p=1 ; sfx=$2 ; shift 2 ;; -t ) flag_t=1 ; shift ;; esac done

Здесь используется нестандартный синтаксис работы с case. Двойное ; (;;) применяется для завершения последовательности действий для определённого условия. Это выполняет декоративную функцию и упрощает восприятие исходного кода.

Я буду очень рад комментарию!

Не переживайте, e-mail нигде не отображается. Обязательные поля помечены *

Навигация по записям