Имеется две схожие программы – getopt
и getopts
.
Основные различия – getopts
является встроенной в bash
командой, тогда как getopt
– вызываемая внешняя (/usr/bin/getopt
):
У getopt
есть несколько недостатков, основная – getopts
внесена в стандарт POSIX для sh
, тогда как getopt
может быть вообще не установлена в системе. Кроме того, это сравнительная сложность, которая в свою очередь вызывает проблемы со стабильностью работы и количеством вероятных ошибок.
С другой стороны – getopt
имеет встроенный механизм обработки --longoption
, вместо коротких опций типа -a
. Однако – к использованию рекомендуется именно getopts
, которую мы и рассмотрим далее.
Перейдём к сути вопроса, и немного опишем применение.
Всем приходилось сталкиваться с программами, которые могут выполнять различные действия в зависимости от указанных им опций. Например:
$ dig localhost
Вернёт результат выполнения прямого запроса к DNS:
;; ANSWER SECTION: localhost. 600 IN A 127.0.0.1
Тогда как будучи запущенной с опцией -x
– выполнит обратный запрос, преобразуя IP-адрес в домен:
$ dig -x 127.0.0.1 ;; ANSWER SECTION: 1.0.0.127.in-addr.arpa. 3600 IN PTR localhost.
Так опциями кардинально меняется выполнение одной и тот же утилиты.
Что бы использовать подобное поведение в собственных скриптах, и при этом не загромождать код ненужными проверками – достаточно выполнить в коде команду getopts
.
Приведём самый простой пример:
$ cat getopts.sh #!/bin/bash while getopts "abc" opt do case $opt in a) echo "Found option $opt";; b) echo "Found option $opt";; c) echo "Found option $opt";; esac done
Тут мы запускаем цикл, который выполняет команду getopts
. В свою очередь getopts
получает список допустимых опций – “abc
” (обратите внимание – допустимые опции указываются без знака тире), и при нахождении одной из них – передаёт её в переменную $opt
. Далее уже case
выполняет действия, связанные с переданной опцией.
При попытке выполнить скрипт с недопустимой опцией – getopts
выведет сообщение об ошибке:
$ ./getopts.sh -h ./getopts.sh: illegal option -- h
Можно отключить такое поведение (verbose mode), добавив символ двоеточия в начале списка допустимых опций:
... while getopts ":abc" opt do ...
Теперь getopts
будет выполняться в silent mode:
$ ./getopts.sh -h $
Усложним пример, и добавим проверку на:
1) обязательное наличие хотя бы одной опции при запуске скрипта;
2) вывод собственного сообщения об ошибке (можно заменить на выполнение какого-то действия), если получена неверная (не найденная в списке) опция:
$ cat getopts.sh #!/bin/bash if [ -z $* ] then echo "No options found!" exit 1 fi while getopts "abc" opt do case $opt in a) echo "Found option $opt";; b) echo "Found option $opt";; c) echo "Found option $opt";; *) echo "No reasonable options found!";; esac done
Оператор if
и утилита test []
в начале проверяют есть ли какое-то содержимое в позиционных аргументах, переданных скрипту и, если ничего не найдено – скрипт выдаст сообщение об ошибке и завершит работу.
Последняя опция в case проверит все, не попавшие под предыдущие определения ключи – и выполнит соответствующее действие.
Для опций можно передавать аргументы. Что бы указать, что для опции аргумент обязателен – установите двоеточие :
после её имени в списке опций:
while getopts "a:bc" opt
Теперь, опция -a будет требовать обязательного указания аргумента.
Тут надо упомянуть о специальных переменных, используемых getopts
:
$OPTIND
– хранит “внутренний индекс”, по которому getopts определяет очередность выполнения опций;
$OPTARG
– содержит аргумент, передаваемый опции;
$OPTERR
– содержит код ошибки, обычно 1.
Изменим наш скрипт, для использования аргумента опцией -a
:
$ cat getopts.sh #!/bin/bash if [ $# -lt 1 ] then echo "No options found!" exit 1 fi while getopts "a:b" opt do case $opt in a) echo "Found option $opt" echo "Found argument for option $opt - $OPTARG" ;; b) echo "Found option $opt";; *) echo "No reasonable options found!";; esac done
Во-первых – мы изменили проверку на наличие опций, теперь проверяется “количество опций больше чем 1”.
Далее – мы добавили обязательное условие при вызове опции -a
– наличие аргумента (a:
). И – добавили вывод на консоль самого аргумента, при указании опции -a
.
Посмотрим, как это будет работать:
$ ./getopts.sh No options found!
$ ./getopts.sh -a ./getopts.sh: option requires an argument -- a No reasonable options found!
$ ./getopts.sh -a arg Found option a Found argument for ption a - arg
Тоже можно сделать для второй опции – -b
:
case $opt in a) echo "Found option $opt" echo "Found argument for ption $opt - $OPTARG" ;; b) echo "Found option $opt" echo "Found argument for ption $opt - $OPTARG" ;; *) echo "No reasonable options found!";; esac
$ ./getopts.sh -a arg1 -b arg2 Found option a Found argument for ption a - arg1 Found option b Found argument for ption b - arg2
Заметьте, что getopts
сам заменяет значение $OPTARG
в зависимости от обрабатываемой опции – тут и используется переменная $OPTIND
.
Однако, тут имеется подводный камень – в случае, если “забыть” указать аргумент для первой опции – вторая опция будет воспринята как аргумент для первой:
$ ./getopts.sh -a -b Found option a Found argument for ption a - -b
Для решения этой проблемы единственный вариант, который пришёл в голову – это добавить функцию, которая будет проверять корректность переданных аргументов:
checkargs () { if [[ $OPTARG =~ ^-[a/b]$ ]] then echo "Unknow argument $OPTARG for option $opt!" exit 1 fi }
Как видно, функция проверяет наличие любой из указанных опций (регулярное выражение ^-[a/b]$
– принять -a
или -b
), и в случае нахождения строки – выдаст соответствующее сообщение.
Теперь – добавим вызов функции перед выполнением действий, связанных с опцией:
case $opt in a) checkargs echo "Found option $opt" echo "Found argument for ption $opt - $OPTARG" ;; b) checkargs echo "Found option $opt" echo "Found argument for ption $opt - $OPTARG" ;; *) echo "No reasonable options found!";; esac
И посмотрим, что получилось:
$ ./getopts.sh -a -b Unknow argument -b for option a!
$ ./getopts.sh -b -a Unknow argument -a for option b!
$ ./getopts.sh -a arg1 -b arg2 Found option a Found argument for ption a - arg1 Found option b Found argument for ption b - arg2
Кстати, одним из достоинств getopts
является то, что вам не надо заботиться о позициях передаваемых аргументов:
$ ./getopts.sh -b arg2 -a arg1 Found option b Found argument for ption b - arg2 Found option a Found argument for ption a - arg1
Так же, не надо беспокоиться о наличии тире перед каждой опцией (тут мы уберём проверку наличия аргументов):
$ ./getopts.sh -ba Found option b Found option a
$ ./getopts.sh -b -a Found option b Found option a
$ ./getopts.sh -ab Found option a Found option b
И, напоследок, пример из рабочего скрипта – та часть, которая демонстрирует работу getopts:
[...] while getopts "x:hlcp" opt; do case $opt in h) usage && exit 1 ;; l) last=1 current="" ;; c) current=1 last="" ;; p) prints=1 exports="" ;; x) exports=1 prints="" ;; ?) usage && exit 1 ;; esac done [...] while [[ $prints ]] do if [[ $last ]] then echo -e "nLast available version in repository is: "$LAST"n" fi if [[ $current ]] then echo -e "nCurrent available version in repository is: "$CURR"n" fi break done [...]
Ссылки по теме
http://wiki.bash-hackers.org
http://programmingexamples.net
http://mywiki.wooledge.org