Использование getopts для обработки длинных и коротких параметров командной строки
-
03-07-2019 - |
Вопрос
Я хочу, чтобы с помощью моего сценария оболочки вызывались длинные и короткие варианты параметров командной строки.
я знаю это getopts
можно использовать, но, как и в Perl, мне не удалось сделать то же самое с оболочкой.
Любые идеи о том, как это можно сделать, чтобы я мог использовать такие варианты, как:
./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/
В приведенном выше примере обе команды означают одно и то же для моей оболочки, но использование getopts
, я не смог реализовать это?
Решение
Можно рассмотреть три реализации:
Встроенный Bash
getopts
.Это не поддерживает длинные имена опций с префиксом в виде двойного тире.Он поддерживает только односимвольные параметры.BSD UNIX реализация автономной
getopt
команда (это то, что использует MacOS).Это также не поддерживает длинные параметры.GNU-реализация автономной
getopt
.ГНУgetopt(3)
(используется командной строкойgetopt(1)
в Linux) поддерживает анализ длинных опций.
Некоторые другие ответы показывают решение для использования встроенной функции bash. getopts
для имитации длинных опционов.Это решение фактически создает короткую опцию с символом «-».Таким образом, вы получаете «--» в качестве флага.Затем все, что следует за этим, становится OPTARG, и вы тестируете OPTARG с помощью вложенного case
.
Это умно, но есть оговорки:
getopts
не может обеспечить соблюдение спецификации opt.Он не может возвращать ошибки, если пользователь указывает недопустимую опцию.Вам придется самостоятельно проверять ошибки при анализе OPTARG.- OPTARG используется для длинного имени опции, что усложняет использование, когда сама длинная опция имеет аргумент.В конечном итоге вам придется кодировать это самостоятельно как дополнительный случай.
Таким образом, хотя можно написать больше кода, чтобы обойти отсутствие поддержки длинных опций, это требует гораздо больше работы и частично сводит на нет цель использования парсера getopt для упрощения вашего кода.
Другие советы
getopt
и getopts
это разные звери, и люди, похоже, немного не понимают, что они делают. getopts
это встроенная команда для bash
для обработки параметров командной строки в цикле и присвоения каждой найденной опции и значения по очереди встроенным переменным, чтобы вы могли их обрабатывать дальше. getopt
, однако это внешняя служебная программа, и она на самом деле не обрабатывает ваши варианты за вас способ, напримербить getopts
, Перл Getopt
модуль или Python optparse
/argparse
модули делают.Все это getopt
делает это канонизацией передаваемых параметров, т.е.преобразуйте их в более стандартную форму, чтобы сценарию оболочки было легче их обрабатывать.Например, приложение getopt
может преобразовать следующее:
myscript -ab infile.txt -ooutfile.txt
в это:
myscript -a -b -o outfile.txt infile.txt
Вам придется выполнить фактическую обработку самостоятельно.Вам не обязательно использовать getopt
вообще, если вы сделаете различные ограничения на способ указания опций:
- используйте только одну опцию для каждого аргумента;
- все параметры идут перед любыми позиционными параметрами (т.е.неопционные аргументы);
- для опций со значениями (например.
-o
выше), значение должно идти как отдельный аргумент (после пробела).
Зачем использовать getopt
вместо getopts
?Основная причина в том, что только GNU getopt
обеспечивает поддержку параметров командной строки с длинными именами.1 (ГНУ getopt
является значением по умолчанию в Linux.Mac OS X и FreeBSD поставляются с базовым и не очень полезным getopt
, но можно установить версию GNU;см. ниже.)
Например, вот пример использования GNU getopt
, из моего сценария под названием javawrap
:
# NOTE: This requires GNU getopt. On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
-n 'javawrap' -- "$@"`
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"
VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
case "$1" in
-v | --verbose ) VERBOSE=true; shift ;;
-d | --debug ) DEBUG=true; shift ;;
-m | --memory ) MEMORY="$2"; shift 2 ;;
--debugfile ) DEBUGFILE="$2"; shift 2 ;;
--minheap )
JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
--maxheap )
JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
-- ) shift; break ;;
* ) break ;;
esac
done
Это позволяет вам указать такие параметры, как --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"
или похожие.Эффект от звонка getopt
заключается в канонизации вариантов --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"
чтобы вам было легче их обрабатывать.Цитирование вокруг "$1"
и "$2"
важен, поскольку он гарантирует правильную обработку аргументов с пробелами.
Если вы удалите первые 9 строк (все до eval set
строка), код будет все еще работают!Однако ваш код будет гораздо более разборчив в том, какие параметры он принимает:В частности, вам придется указать все параметры в «канонической» форме, описанной выше.С использованием getopt
, однако вы можете группировать однобуквенные опции, использовать более короткие однозначные формы длинных опций, использовать либо --file foo.txt
или --file=foo.txt
стиль, используйте либо -m 4096
или -m4096
стиль, сочетание опций и не-опций в любом порядке и т. д. getopt
также выводит сообщение об ошибке, если обнаружены нераспознанные или неоднозначные параметры.
ПРИМЕЧАНИЕ:На самом деле их два Абсолютно другой версии getopt
, базовый getopt
и ГНУ getopt
, с разными функциями и разными соглашениями о вызовах.2 Базовый getopt
совсем сломан:Он не только не обрабатывает длинные параметры, но также не может обрабатывать вставленные пробелы внутри аргументов или пустые аргументы, тогда как getopts
делает это правильно.Приведенный выше код не будет работать в базовом режиме. getopt
.ГНУ getopt
устанавливается по умолчанию в Linux, но в Mac OS X и FreeBSD его необходимо устанавливать отдельно.В Mac OS X установите MacPorts (http://www.macports.org), а затем сделать sudo port install getopt
установить GNU getopt
(обычно в /opt/local/bin
), и убедитесь, что /opt/local/bin
находится на пути вашей оболочки впереди /usr/bin
.Во FreeBSD установите misc/getopt
.
Краткое руководство по изменению примера кода для вашей собственной программы:Из первых нескольких строк все является «шаблоном», который должен остаться прежним, за исключением строки, вызывающей getopt
.Вам следует изменить название программы после -n
, укажите короткие параметры после -o
, и длинные опции после --long
.Поставьте двоеточие после опций, которые принимают значение.
Наконец, если вы видите код, который только что set
вместо eval set
, оно было написано для BSD getopt
.Вам следует изменить его, чтобы использовать eval set
стиль, который отлично работает с обеими версиями getopt
, в то время как равнина set
не работает с GNU getopt
.
1На самом деле, getopts
в ksh93
поддерживает параметры с длинными именами, но эта оболочка используется не так часто, как bash
.В zsh
, использовать zparseopts
чтобы получить эту функциональность.
2Технически, «GNU getopt
«это неправильное употребление;эта версия на самом деле была написана для Linux, а не для проекта GNU.Однако он следует всем соглашениям GNU и термину «GNU». getopt
«обычно используется (например,во FreeBSD).
Встроенную функцию getopts Bash можно использовать для анализа длинных параметров, поместив в optspec символ тире, за которым следует двоеточие:
#!/usr/bin/env bash
optspec=":hv-:"
while getopts "$optspec" optchar; do
case "${optchar}" in
-)
case "${OPTARG}" in
loglevel)
val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
;;
loglevel=*)
val=${OPTARG#*=}
opt=${OPTARG%=$val}
echo "Parsing option: '--${opt}', value: '${val}'" >&2
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
fi
;;
esac;;
h)
echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
exit 2
;;
v)
echo "Parsing option: '-${optchar}'" >&2
;;
*)
if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
echo "Non-option argument: '-${OPTARG}'" >&2
fi
;;
esac
done
После копирования в исполняемый файл имя=getopts_test.sh
в текущий рабочий каталог, можно получить результат, например
$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'
Очевидно, getopts не работает. OPTERR
проверка и анализ аргументов опций для длинных опций.Фрагмент сценария выше показывает, как это можно сделать вручную.Основной принцип работает и в оболочке Debian Almquist («тире»).Обратите внимание на особый случай:
getopts -- "-:" ## without the option terminator "-- " bash complains about "-:"
getopts "-:" ## this works in the Debian Almquist shell ("dash")
Обратите внимание, что, поскольку GreyCat из http://mywiki.wooledge.org/BashFAQ указывает, что этот трюк использует нестандартное поведение оболочки, которое допускает аргумент-опцию (т.имя файла в "-f имя_файла") должно быть объединено с опцией (как в "-fимя_файла").А ПОСИКС стандарт говорит, что между ними должен быть пробел, который в случае «--longoption» прекратит анализ параметров и превратит все длинные параметры в аргументы, не являющиеся параметрами.
Встроенный getopts
команда по-прежнему, AFAIK, ограничена только односимвольными параметрами.
Есть (или была) внешняя программа getopt
это реорганизовало бы набор опций, чтобы его было легче анализировать.Вы можете адаптировать этот дизайн и для работы с длинными вариантами.Пример использования:
aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
case "$1" in
(-a) aflag=yes;;
(-b) bflag=yes;;
(-f) flist="$flist $2"; shift;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
# Process remaining non-option arguments
...
Вы можете использовать аналогичную схему с getoptlong
команда.
Обратите внимание, что фундаментальная слабость внешней getopt
Программа заключается в сложности обработки аргументов с пробелами в них и в точном сохранении этих пробелов.Вот почему встроенный getopts
превосходит этот вариант, хотя и ограничен тем фактом, что он обрабатывает только однобуквенные варианты.
Вот пример, в котором на самом деле используется getopt с длинными параметрами:
aflag=no
bflag=no
cargument=none
# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
# something went wrong, getopt will put out an error message for us
exit 1
fi
set -- $options
while [ $# -gt 0 ]
do
case $1 in
-a|--along) aflag="yes" ;;
-b|--blong) bflag="yes" ;;
# for options with required arguments, an additional shift is required
-c|--clong) cargument="$2" ; shift;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
Длинные параметры можно анализировать по стандарту getopts
встроен в качестве «аргументов» для -
"вариант"
Это переносимая и встроенная оболочка POSIX — никаких внешних программ или bashisms не требуется.
В этом руководстве длинные параметры используются в качестве аргументов -
вариант, так что --alpha
видит getopts
как -
с аргументом alpha
и --bravo=foo
рассматривается как -
с аргументом bravo=foo
.Истинный аргумент можно получить с помощью простой замены: ${OPTARG#*=}
.
В этом примере -b
(и его полная форма, --bravo
) имеет обязательную опцию (обратите внимание на ручную реконструкцию принудительного применения этой опции для полной формы).Нелогические параметры длинных аргументов идут после знаков равенства, например --bravo=foo
(разделители пробелов для длинных опций будет сложно реализовать).
Потому что это использует getopts
, это решение поддерживает использование, например cmd -ac --bravo=foo -d FILE
(который имеет комбинированные варианты -a
и -c
и чередует длинные варианты со стандартными вариантами), в то время как большинство других ответов здесь либо с трудом, либо не могут этого сделать.
while getopts ab:c-: arg; do
case $arg in
a ) ARG_A=true ;;
b ) ARG_B="$OPTARG" ;;
c ) ARG_C=true ;;
- ) LONG_OPTARG="${OPTARG#*=}"
case $OPTARG in
alpha ) ARG_A=true ;;
bravo=?* ) ARG_B="$LONG_OPTARG" ;;
bravo* ) echo "No arg for --$OPTARG option" >&2; exit 2 ;;
charlie ) ARG_C=true ;;
alpha* | charlie* )
echo "No arg allowed for --$OPTARG option" >&2; exit 2 ;;
'' ) break ;; # "--" terminates argument processing
* ) echo "Illegal option --$OPTARG" >&2; exit 2 ;;
esac ;;
\? ) exit 2 ;; # getopts already reported the illegal option
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list
Когда аргумент представляет собой тире (-
), он имеет еще два компонента:имя флага и (необязательно) его аргумент.Я разделяю их стандартным способом, как это делает любая команда, первым знаком равенства (=
). $LONG_OPTARG
следовательно, является лишь содержанием $OPTARG
без имени флага или знака равенства.
Внутренний case
реализует длинные параметры вручную, поэтому требует некоторой очистки:
bravo=?
Матчи--bravo=foo
но нет--bravo=
(примечание:case
останавливается после первого матча)bravo*
следует, отмечая отсутствующий обязательный аргумент в--bravo
и--bravo=
alpha* | charlie*
перехватывает аргументы, переданные опциям, которые их не поддерживают''
присутствует для поддержки не-опций, которые начинаются с тире*
перехватывает все остальные длинные параметры и воссоздает ошибку, выданную getopts для недопустимого параметра
Вам не обязательно нужны все эти предметы домашнего обихода.Например, возможно, вы хотите --bravo
иметь необязательный аргумент (который -b
не может поддерживаться из-за ограничений в getopts
).Просто удалите =?
и соответствующий случай сбоя, а затем позвоните ${ARG_B:=$DEFAULT_ARG_B}
первый раз, когда вы используете $ARG_B
.
Чтобы принять длинные параметры с аргументами, разделенными пробелами, вам понадобится (достаточно безопасный) eval
:
bravo=?* ) ARG_B="$LONG_OPTARG" ;;
bravo ) eval "ARG_B=\"\$$OPTIND\""
if [ -z "$ARG_B" ]; then
echo "No arg for --$OPTARG option" >&2; exit 2
fi
OPTIND=$((OPTIND+1)) ;;
bravo* ) echo "No arg for --$OPTARG option" >&2; exit 2 ;;
Этот дополнительный пункт, добавленный непосредственно после =
версия назначения, запускает eval
для интерпретации значения параметра, следующего за опцией, интерпретируемой в данный момент.Bash может сделать это более чисто, используя косвенное расширение, ARG_B="${!OPTIND}"
и Zsh может сделать это с помощью расширение параметров P
флаг, ARGB="${(P)OPTIND}"
, но eval
необходим для полной переносимости POSIX. $OPTIND
это число, относящееся к следующему аргументу оболочки, скажем $2
(значение $1
является --bravo
).А eval
тогда бы интерпретировал ARG_B="$2"
и поскольку это заключено в кавычки, оно защищено от злоупотреблений (я не смог найти способа заставить его сделать что-то неподобающее).
Гарантия непустого значения нетривиальна и требует его фактической проверки, поэтому у нас есть условие и в этом случае генерируется фатальная ошибка.Если вы позволите ему быть пустым, вам нужно будет обусловить увеличение $OPTIND
или иначе попадете в бесконечный цикл. [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))
следует сделать.
Заключительная часть этого дополнительного пункта увеличивается $OPTIND
правильно усвоить аргументы варианта и перейти к следующему варианту.
Взгляни на шФлаги которая представляет собой переносимую библиотеку оболочки (что означает:sh, bash, Dash, ksh, zsh в Linux, Solaris и т. д.).
Благодаря этому добавление новых флагов становится таким же простым, как добавление одной строки в ваш скрипт, а также предусмотрена автоматически генерируемая функция использования.
Вот простой Hello, world!
с использованием шФлаг:
#!/bin/sh
# source shflags from current directory
. ./shflags
# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'
# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# say hello
echo "Hello, ${FLAGS_name}!"
Для операционных систем с расширенным getopt, поддерживающим длинные параметры (например,Linux), вы можете сделать:
$ ./hello_world.sh --name Kate
Hello, Kate!
В остальном необходимо использовать короткий вариант:
$ ./hello_world.sh -n Kate
Hello, Kate!
Добавить новый флаг так же просто, как добавить новый DEFINE_ call
.
С использованием getopts
с короткими/длинными опциями и аргументами
Работает со всеми комбинациями, например:
- фубар -f --бар
- фубар --foo -b
- foobar -bf --bar --foobar
- foobar -fbFBAshorty --bar -FB --arguments=longhorn
- foobar -fA "текстовый коротышка" -B --arguments="текстовый лонгхорн"
- bash foobar -F --barfoo
- ш foobar -B --foobar - ...
- bash ./foobar -F --bar
Некоторые объявления для этого примера
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty
Как будет выглядеть функция использования
function _usage()
{
###### U S A G E : Help and ERROR ######
cat <<EOF
foobar $Options
$*
Usage: foobar <[options]>
Options:
-b --bar Set bar to yes ($foo)
-f --foo Set foo to yes ($bart)
-h --help Show this message
-A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
-B --barfoo Set barfoo to yes ($barfoo)
-F --foobar Set foobar to yes ($foobar)
EOF
}
[ $# = 0 ] && _usage " >>>>>>>> no options given "
getops
с длинными/короткими флагами, а также длинными аргументами
while getopts ':bfh-A:BF' OPTION ; do
case "$OPTION" in
b ) sbar=yes ;;
f ) sfoo=yes ;;
h ) _usage ;;
A ) sarguments=yes;sARG="$OPTARG" ;;
B ) sbarfoo=yes ;;
F ) sfoobar=yes ;;
- ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
eval OPTION="\$$optind"
OPTARG=$(echo $OPTION | cut -d'=' -f2)
OPTION=$(echo $OPTION | cut -d'=' -f1)
case $OPTION in
--foo ) lfoo=yes ;;
--bar ) lbar=yes ;;
--foobar ) lfoobar=yes ;;
--barfoo ) lbarfoo=yes ;;
--help ) _usage ;;
--arguments ) larguments=yes;lARG="$OPTARG" ;;
* ) _usage " Long: >>>>>>>> invalid options (long) " ;;
esac
OPTIND=1
shift
;;
? ) _usage "Short: >>>>>>>> invalid options (short) " ;;
esac
done
Выход
##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo : $sfoo long-foo : $lfoo"
echo "RESULT short-bar : $sbar long-bar : $lbar"
echo "RESULT short-foobar : $sfoobar long-foobar : $lfoobar"
echo "RESULT short-barfoo : $sbarfoo long-barfoo : $lbarfoo"
echo "RESULT short-arguments: $sarguments with Argument = \"$sARG\" long-arguments: $larguments and $lARG"
Объединение вышеперечисленного в единый сценарий
#!/bin/bash
# foobar: getopts with short and long options AND arguments
function _cleanup ()
{
unset -f _usage _cleanup ; return 0
}
## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN
###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty
function _usage()
{
###### U S A G E : Help and ERROR ######
cat <<EOF
foobar $Options
$*
Usage: foobar <[options]>
Options:
-b --bar Set bar to yes ($foo)
-f --foo Set foo to yes ($bart)
-h --help Show this message
-A --arguments=... Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
-B --barfoo Set barfoo to yes ($barfoo)
-F --foobar Set foobar to yes ($foobar)
EOF
}
[ $# = 0 ] && _usage " >>>>>>>> no options given "
##################################################################
####### "getopts" with: short options AND long options #######
####### AND short/long arguments #######
while getopts ':bfh-A:BF' OPTION ; do
case "$OPTION" in
b ) sbar=yes ;;
f ) sfoo=yes ;;
h ) _usage ;;
A ) sarguments=yes;sARG="$OPTARG" ;;
B ) sbarfoo=yes ;;
F ) sfoobar=yes ;;
- ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
eval OPTION="\$$optind"
OPTARG=$(echo $OPTION | cut -d'=' -f2)
OPTION=$(echo $OPTION | cut -d'=' -f1)
case $OPTION in
--foo ) lfoo=yes ;;
--bar ) lbar=yes ;;
--foobar ) lfoobar=yes ;;
--barfoo ) lbarfoo=yes ;;
--help ) _usage ;;
--arguments ) larguments=yes;lARG="$OPTARG" ;;
* ) _usage " Long: >>>>>>>> invalid options (long) " ;;
esac
OPTIND=1
shift
;;
? ) _usage "Short: >>>>>>>> invalid options (short) " ;;
esac
done
Другой путь...
# translate long options to short
for arg
do
delim=""
case "$arg" in
--help) args="${args}-h ";;
--verbose) args="${args}-v ";;
--config) args="${args}-c ";;
# pass through anything else
*) [[ "${arg:0:1}" == "-" ]] || delim="\""
args="${args}${delim}${arg}${delim} ";;
esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
case $opt in
h) usage ;;
v) VERBOSE=true ;;
c) source $OPTARG ;;
\?) usage ;;
:)
echo "option -$OPTARG requires an argument"
usage
;;
esac
done
Я как-то решил так:
# A string with command options
options=$@
# An array with all the arguments
arguments=($options)
# Loop index
index=0
for argument in $options
do
# Incrementing index
index=`expr $index + 1`
# The conditions
case $argument in
-a) echo "key $argument value ${arguments[index]}" ;;
-abc) echo "key $argument value ${arguments[index]}" ;;
esac
done
exit;
Я тупой или что? getopt
и getopts
такие запутанные.
В случае, если вы не хотите getopt
зависимость, вы можете сделать это:
while test $# -gt 0
do
case $1 in
# Normal option processing
-h | --help)
# usage and help
;;
-v | --version)
# version info
;;
# ...
# Special cases
--)
break
;;
--*)
# error unknown (long) option $1
;;
-?)
# error unknown (short) option $1
;;
# FUN STUFF HERE:
# Split apart combined short options
-*)
split=$1
shift
set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
continue
;;
# Done with options
*)
break
;;
esac
# for testing purposes:
echo "$1"
shift
done
Конечно, тогда вы не сможете использовать длинные варианты стиля с одним тире.И если вы хотите добавить сокращенные версии (например.--verbos вместо --verbose), то вам придется добавить их вручную.
Но если вы хотите получить getopts
функциональность наряду с длинными опциями, это простой способ сделать это.
Я также поместил этот фрагмент в суть.
Встроенный getopts
не могу этого сделать.Есть внешний получить выбор(1) программа, которая может это сделать, но вы можете получить ее только в Linux из утилита-Linux упаковка.Он поставляется с примером сценария getopt-parse.bash.
Существует также getopts_long
написан как функция оболочки.
#!/bin/bash
while getopts "abc:d:" flag
do
case $flag in
a) echo "[getopts:$OPTIND]==> -$flag";;
b) echo "[getopts:$OPTIND]==> -$flag";;
c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
esac
done
shift $((OPTIND-1))
echo "[otheropts]==> $@"
exit
.
#!/bin/bash
until [ -z "$1" ]; do
case $1 in
"--dlong")
shift
if [ "${1:1:0}" != "-" ]
then
echo "==> dlong $1"
shift
fi;;
*) echo "==> other $1"; shift;;
esac
done
exit
В ksh93
, getopts
поддерживает длинные имена...
while getopts "f(file):s(server):" flag
do
echo "$flag" $OPTIND $OPTARG
done
По крайней мере, так сказано в учебниках, которые я нашел.Попробуйте и посмотрите.
Изобретение еще одной версии колеса...
Эта функция является (надеюсь) POSIX-совместимой заменой простой оболочки для GNU getopt.Он поддерживает короткие/длинные параметры, которые могут принимать обязательные/необязательные аргументы/без аргументов, а способ указания параметров почти идентичен GNU getopt, поэтому преобразование тривиально.
Конечно, это по-прежнему значительный кусок кода, который нужно добавить в сценарий, но он занимает примерно половину строк известной функции оболочки getopt_long и может быть предпочтительнее в тех случаях, когда вы просто хотите заменить существующие варианты использования GNU getopt.
Это довольно новый код, поэтому YMMV (и обязательно дайте мне знать, если он по какой-либо причине на самом деле не совместим с POSIX - переносимость была задумана с самого начала, но у меня нет полезной тестовой среды POSIX).
Ниже приведен код и пример использования:
#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105
# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
local param
for param; do
printf %s\\n "$param" \
| sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
done
printf %s\\n " "
}
# Exit with status $1 after displaying error message $2.
exiterr () {
printf %s\\n "$2" >&2
exit $1
}
# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
local shortopts longopts \
arg argtype getopt nonopt opt optchar optword suffix
shortopts="$1"
longopts="$2"
shift 2
getopt=
nonopt=
while [ $# -gt 0 ]; do
opt=
arg=
argtype=
case "$1" in
# '--' means don't parse the remaining options
( -- ) {
getopt="${getopt}$(save "$@")"
shift $#
break
};;
# process short option
( -[!-]* ) { # -x[foo]
suffix=${1#-?} # foo
opt=${1%$suffix} # -x
optchar=${opt#-} # x
case "${shortopts}" in
( *${optchar}::* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *${optchar}:* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 "$1 requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 "$1 requires an argument";;
esac
fi
};;
( *${optchar}* ) { # no argument
argtype=none
arg=
shift
# Handle multiple no-argument parameters combined as
# -xyz instead of -x -y -z. If we have just shifted
# parameter -xyz, we now replace it with -yz (which
# will be processed in the next iteration).
if [ -n "${suffix}" ]; then
eval "set -- $(save "-${suffix}")$(save "$@")"
fi
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# process long option
( --?* ) { # --xarg[=foo]
suffix=${1#*=} # foo (unless there was no =)
if [ "${suffix}" = "$1" ]; then
suffix=
fi
opt=${1%=$suffix} # --xarg
optword=${opt#--} # xarg
case ",${longopts}," in
( *,${optword}::,* ) { # optional argument
argtype=optional
arg="${suffix}"
shift
};;
( *,${optword}:,* ) { # required argument
argtype=required
if [ -n "${suffix}" ]; then
arg="${suffix}"
shift
else
case "$2" in
( -* ) exiterr 1 \
"--${optword} requires an argument";;
( ?* ) arg="$2"; shift 2;;
( * ) exiterr 1 \
"--${optword} requires an argument";;
esac
fi
};;
( *,${optword},* ) { # no argument
if [ -n "${suffix}" ]; then
exiterr 1 "--${optword} does not take an argument"
fi
argtype=none
arg=
shift
};;
( * ) exiterr 1 "Unknown option $1";;
esac
};;
# any other parameters starting with -
( -* ) exiterr 1 "Unknown option $1";;
# remember non-option parameters
( * ) nonopt="${nonopt}$(save "$1")"; shift;;
esac
if [ -n "${opt}" ]; then
getopt="${getopt}$(save "$opt")"
case "${argtype}" in
( optional|required ) {
getopt="${getopt}$(save "$arg")"
};;
esac
fi
done
# Generate function output, suitable for:
# eval "set -- $(posix_getopt ...)"
printf %s "${getopt}"
if [ -n "${nonopt}" ]; then
printf %s "$(save "--")${nonopt}"
fi
}
Пример использования:
# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
#eval set -- ${opts}
eval "set -- ${opts}"
while [ $# -gt 0 ]; do
case "$1" in
( -- ) shift; break;;
( -h|--help ) help=1; shift; break;;
( -v|--version ) version_help=1; shift; break;;
( -d|--directory ) dir=$2; shift 2;;
( -c|--client ) useclient=1; client=$2; shift 2;;
( -s|--server ) startserver=1; server_name=$2; shift 2;;
( -L|--load ) load=$2; shift 2;;
( -D|--delete ) delete=1; shift;;
esac
done
else
shorthelp=1 # getopt returned (and reported) an error.
fi
Я пишу сценарии оболочки лишь время от времени и отвыкаю от практики, поэтому буду рад любым отзывам.
Используя стратегию, предложенную @Arvid Requate, мы заметили некоторые ошибки пользователей.Пользователь, который забудет указать значение, случайно воспримет имя следующей опции как значение:
./getopts_test.sh --loglevel= --toc=TRUE
приведет к тому, что значение «loglevel» будет отображаться как «--toc=TRUE».Этого можно избежать.
Я адаптировал некоторые идеи по проверке ошибок пользователя для CLI из http://mwiki.wooledge.org/BashFAQ/035 обсуждение ручного разбора.Я включил проверку ошибок в обработку аргументов «-» и «--».
Затем я начал возиться с синтаксисом, поэтому любые ошибки здесь — это исключительно моя вина, а не первоначальных авторов.
Мой подход помогает пользователям, которые предпочитают открывать длинные позиции со знаком равенства или без него.То есть на «--loglevel 9» он должен иметь тот же ответ, что и на «--loglevel=9».В методе --/space невозможно точно узнать, забыл ли пользователь аргумент, поэтому необходимо догадаться.
- если у пользователя есть формат длинного знака/равно (--opt=), то пробел после = вызывает ошибку, поскольку аргумент не был указан.
- если у пользователя есть длинные/пробелные аргументы (--opt), этот сценарий вызывает сбой, если аргумент не следует (конец команды) или если аргумент начинается с тире)
Если вы только начинаете это делать, обратите внимание на интересную разницу между форматами «--opt=value» и «--opt value».Со знаком равенства аргумент командной строки рассматривается как «opt=value», а обрабатываемая работа представляет собой синтаксический анализ строки и отделяется знаком «=".Напротив, при использовании «--opt value» имя аргумента — «opt», и перед нами стоит задача получить следующее значение, указанное в командной строке.Именно здесь @Arvid Requate использовал ${!OPTIND}, косвенную ссылку.Я до сих пор этого не понимаю, ну вообще, и комментарии в BashFAQ вроде бы предостерегают от такого стиля (http://mywiki.wooledge.org/BashFAQ/006).Кстати, я не думаю, что комментарии предыдущего автора о важности OPTIND=$(( $OPTIND + 1)) верны.Я хочу сказать, что не вижу никакого вреда в том, чтобы его пропустить.
В последней версии этого скрипта флаг -v означает ПОДРОБНУЮ распечатку.
Сохраните его в файле с именем «cli-5.sh», сделайте исполняемым, и любой из них будет работать или потерпеть неудачу желаемым образом.
./cli-5.sh -v --loglevel=44 --toc TRUE
./cli-5.sh -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9
./cli-5.sh --toc FALSE --loglevel=77
./cli-5.sh --toc=FALSE --loglevel=77
./cli-5.sh -l99 -t yyy
./cli-5.sh -l 99 -t yyy
Вот пример вывода проверки ошибок на пользовательском вводе
$ ./cli-5.sh --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh --toc= --loglevel=77
ERROR: value for toc undefined
Вам следует рассмотреть возможность включения -v, потому что он распечатывает внутренние данные OPTIND и OPTARG.
#/usr/bin/env bash
## Paul Johnson
## 20171016
##
## Combines ideas from
## https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035
# What I don't understand yet:
# In @Arvid REquate's answer, we have
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!
die() {
printf '%s\n' "$1" >&2
exit 1
}
printparse(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
fi
}
showme(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'VERBOSE: %s\n' "$1" >&2;
fi
}
VERBOSE=0
loglevel=0
toc="TRUE"
optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do
showme "OPTARG: ${OPTARG[*]}"
showme "OPTIND: ${OPTIND[*]}"
case "${OPTCHAR}" in
-)
case "${OPTARG}" in
loglevel) #argument has no equal sign
opt=${OPTARG}
val="${!OPTIND}"
## check value. If negative, assume user forgot value
showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
if [[ "$val" == -* ]]; then
die "ERROR: $opt value must not have dash at beginning"
fi
## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
printparse "--${OPTARG}" " " "${val}"
loglevel="${val}"
shift
;;
loglevel=*) #argument has equal sign
opt=${OPTARG%=*}
val=${OPTARG#*=}
if [ "${OPTARG#*=}" ]; then
printparse "--${opt}" "=" "${val}"
loglevel="${val}"
## shift CAUTION don't shift this, fails othewise
else
die "ERROR: $opt value must be supplied"
fi
;;
toc) #argument has no equal sign
opt=${OPTARG}
val="${!OPTIND}"
## check value. If negative, assume user forgot value
showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
if [[ "$val" == -* ]]; then
die "ERROR: $opt value must not have dash at beginning"
fi
## OPTIND=$(( $OPTIND + 1 )) #??
printparse "--${opt}" " " "${val}"
toc="${val}"
shift
;;
toc=*) #argument has equal sign
opt=${OPTARG%=*}
val=${OPTARG#*=}
if [ "${OPTARG#*=}" ]; then
toc=${val}
printparse "--$opt" " -> " "$toc"
##shift ## NO! dont shift this
else
die "ERROR: value for $opt undefined"
fi
;;
help)
echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
exit 2
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
fi
;;
esac;;
h|-\?|--help)
## must rewrite this for all of the arguments
echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
exit 2
;;
l)
loglevel=${OPTARG}
printparse "-l" " " "${loglevel}"
;;
t)
toc=${OPTARG}
;;
v)
VERBOSE=1
;;
*)
if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
echo "Non-option argument: '-${OPTARG}'" >&2
fi
;;
esac
done
echo "
After Parsing values
"
echo "loglevel $loglevel"
echo "toc $toc"
Здесь вы можете найти несколько различных подходов для анализа сложных параметров в bash:http://mywiki.wooledge.org/ComplexOptionParsing
Я действительно создал следующий, и я думаю, что это хороший, потому что это минимальный код, и работают как длинные, так и короткие варианты.При таком подходе длинный вариант также может иметь несколько аргументов.
#!/bin/bash
# Uses bash extensions. Not portable as written.
declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
case "${opt}" in
-) #OPTARG is name-of-long-option or name-of-long-option=value
if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
then
opt=${OPTARG/=*/}
OPTARG=${OPTARG#*=}
((OPTIND--))
else #with this --key value1 value2 format multiple arguments are possible
opt="$OPTARG"
OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
fi
((OPTIND+=longoptspec[$opt]))
continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
;;
loglevel)
loglevel=$OPTARG
;;
h|help)
echo "usage: $0 [--loglevel[=]<value>]" >&2
exit 2
;;
esac
break; done
done
# End of file
Я уже давно работаю над этой темой...и создал свою собственную библиотеку, которую вам нужно будет включить в основной скрипт.Видеть libopt4shell и cd2mpc для примера.Надеюсь, поможет !
Улучшенное решение:
# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after "--" in option fields.
for ((i=1;$#;i++)) ; do
case "$1" in
--)
# [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
EndOpt=1 ;;&
--version) ((EndOpt)) && args[$i]="$1" || args[$i]="-V";;
# default case : short option use the first char of the long option:
--?*) ((EndOpt)) && args[$i]="$1" || args[$i]="-${1:2:1}";;
# pass through anything else:
*) args[$i]="$1" ;;
esac
shift
done
# reset the translated args
set -- "${args[@]}"
function usage {
echo "Usage: $0 [options] files" >&2
exit $1
}
# now we can process with getopt
while getopts ":hvVc:" opt; do
case $opt in
h) usage ;;
v) VERBOSE=true ;;
V) echo $Version ; exit ;;
c) source $OPTARG ;;
\?) echo "unrecognized option: -$opt" ; usage -1 ;;
:)
echo "option -$OPTARG requires an argument"
usage -1
;;
esac
done
shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift
Возможно, проще использовать ksh только для части getopts, если нужны длинные параметры командной строки, так как там это проще сделать.
# Working Getopts Long => KSH
#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"
while getopts "$USAGE" optchar ; do
case $optchar in
s) echo "Displaying Configuration" ;;
c) echo "Creating Database $OPTARG" ;;
l) echo "Creating Listener LISTENER_$OPTARG" ;;
g) echo "Generating Scripts for Database $OPTARG" ;;
r) echo "Removing Database $OPTARG" ;;
x) echo "Removing Listener LISTENER_$OPTARG" ;;
t) echo "Creating Database Template" ;;
h) echo "Help" ;;
esac
done
У меня пока недостаточно представителей, чтобы комментировать или голосовать за его решение, но ответ малого бизнеса сработало для меня очень хорошо.Единственная проблема, с которой я столкнулся, заключалась в том, что аргументы заключались в одинарные кавычки (поэтому мне пришлось их удалить).
Я также добавил несколько примеров использования и текст справки.Я включил сюда свою слегка расширенную версию:
#!/bin/bash
# getopt example
# from: https://stackoverflow.com/questions/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
" USAGE:\n
Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n
Accepts the following forms:\n\n
getopt-example.sh -a -b -c value-for-c some-arg\n
getopt-example.sh -c value-for-c -a -b some-arg\n
getopt-example.sh -abc some-arg\n
getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
getopt-example.sh some-arg --clong value-for-c\n
getopt-example.sh
"
aflag=false
bflag=false
cargument=""
# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
# something went wrong, getopt will put out an error message for us
exit 1
fi
set -- $options
while [ $# -gt 0 ]
do
case $1 in
-a|--along) aflag=true ;;
-b|--blong) bflag=true ;;
# for options with required arguments, an additional shift is required
-c|--clong) cargument="$2" ; shift;;
-h|--help|-\?) echo -e $HELP_TEXT; exit;;
(--) shift; break;;
(-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
(*) break;;
esac
shift
done
# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g" (just leading/trailing) or | sed -e "s/'//g" (all)
echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}
while [ $# -gt 0 ]
do
echo arg=$1
shift
if [[ $aflag == true ]]; then
echo a is true
fi
done
Мне хотелось чего-то без внешних зависимостей, со строгой поддержкой bash (-u), и мне нужно было, чтобы это работало даже со старыми версиями bash.Это обрабатывает различные типы параметров:
- короткие логические значения (-h)
- короткие параметры (-i "image.jpg")
- длинные логические значения (--help)
- равно опциям (--file="filename.ext")
- параметры пробела (--file "filename.ext")
- объединенные логические значения (-hvm)
Просто вставьте следующее в начало вашего скрипта:
# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
for param in $1 ; do
local variants=${param//\|/ }
for variant in $variants ; do
if [[ "$variant" = "$2" ]] ; then
# Update the key to match the long version
local arr=(${param//\|/ })
let last=${#arr[@]}-1
key="${arr[$last]}"
return 0
fi
done
done
return 1
}
# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
# # First, set your defaults
# param_help=false
# param_path="."
# param_file=false
# param_image=false
# param_image_lossy=true
# # Define allowed parameters
# allowed_params="h|?|help p|path f|file i|image image-lossy"
# # Get parameters from the arguments provided
# _get_params $*
#
# Parameters will be converted into safe variable names like:
# param_help,
# param_path,
# param_file,
# param_image,
# param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
# -i "path/goes/here"
# --image "path/goes/here"
# --image="path/goes/here"
# --image=path/goes/here
# These would all result in effectively the same thing:
# param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
# -vhm is the same as -v -h -m
_get_params(){
local param_pair
local key
local value
local shift_count
while : ; do
# Ensure we have a valid param. Allows this to work even in -u mode.
if [[ $# == 0 || -z $1 ]] ; then
break
fi
# Split the argument if it contains "="
param_pair=(${1//=/ })
# Remove preceeding dashes
key="${param_pair[0]#--}"
# Check for concatinated boolean short parameters.
local nodash="${key#-}"
local breakout=false
if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
# Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
local short_param_count=${#nodash}
let new_arg_count=$#+$short_param_count-1
local new_args=""
# $str_pos is the current position in the short param string $nodash
for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
# The first character becomes the current key
if [ $str_pos -eq 0 ] ; then
key="${nodash:$str_pos:1}"
breakout=true
fi
# $arg_pos is the current position in the constructed arguments list
let arg_pos=$str_pos+1
if [ $arg_pos -gt $short_param_count ] ; then
# handle other arguments
let orignal_arg_number=$arg_pos-$short_param_count+1
local new_arg="${!orignal_arg_number}"
else
# break out our one argument into new ones
local new_arg="-${nodash:$str_pos:1}"
fi
new_args="$new_args \"$new_arg\""
done
# remove the preceding space and set the new arguments
eval set -- "${new_args# }"
fi
if ! $breakout ; then
key="$nodash"
fi
# By default we expect to shift one argument at a time
shift_count=1
if [ "${#param_pair[@]}" -gt "1" ] ; then
# This is a param with equals notation
value="${param_pair[1]}"
else
# This is either a boolean param and there is no value,
# or the value is the next command line argument
# Assume the value is a boolean true, unless the next argument is found to be a value.
value=true
if [[ $# -gt 1 && -n "$2" ]]; then
local nodash="${2#-}"
if [ "$nodash" = "$2" ]; then
# The next argument has NO preceding dash so it is a value
value="$2"
shift_count=2
fi
fi
fi
# Check that the param being passed is one of the allowed params
if _param_variant "$allowed_params" "$key" ; then
# --key-name will now become param_key_name
eval param_${key//-/_}="$value"
else
printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
fi
shift $shift_count
done
}
И используйте его так:
# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85
# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"
# Get the params from arguments provided
_get_params $*
Принятый ответ очень хорошо указывает на все недостатки встроенной функции bash. getopts
.Ответ заканчивается:
Таким образом, хотя можно написать больше кода, чтобы обойти отсутствие поддержки длинных опций, это требует гораздо больше работы и частично сводит на нет цель использования парсера getopt для упрощения вашего кода.
И хотя я в принципе согласен с этим утверждением, я чувствую, что то количество раз, когда мы все реализовывали эту функцию в различных сценариях, оправдывает приложение некоторых усилий для создания «стандартизированного», хорошо протестированного решения.
Таким образом, я «обновил» встроенный bash. getopts
путем реализации getopts_long
на чистом bash, без внешних зависимостей.Использование функции на 100% совместимо со встроенным getopts
.
Включив getopts_long
(который размещен на GitHub по адресу https://github.com/UmkaDK/getopts_long) в скрипте ответ на исходный вопрос можно реализовать так же просто, как:
source "${PATH_TO}/getopts_long.bash"
while getopts_long ':c: copyfile:' OPTKEY; do
case ${OPTKEY} in
'c'|'copyfile')
echo 'file supplied -- ${OPTARG}'
;;
'?')
echo "INVALID OPTION -- ${OPTARG}" >&2
exit 1
;;
':')
echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
exit 1
;;
*)
echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
exit 1
;;
esac
done
shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift
Чтобы сохранить кроссплатформенную совместимость и избежать зависимости от внешних исполняемых файлов, я перенес часть кода с другого языка.
Я считаю, что это очень просто в использовании, вот пример:
ArgParser::addArg "[h]elp" false "This list"
ArgParser::addArg "[q]uiet" false "Supress output"
ArgParser::addArg "[s]leep" 1 "Seconds to sleep"
ArgParser::addArg "v" 1 "Verbose mode"
ArgParser::parse "$@"
ArgParser::isset help && ArgParser::showArgs
ArgParser::isset "quiet" \
&& echo "Quiet!" \
|| echo "Noisy!"
local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
&& echo "Sleep for $__sleep seconds" \
|| echo "No value passed for sleep"
# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"
Требуемый BASH немного длиннее, чем мог бы быть, но я хотел избежать зависимости от ассоциативных массивов BASH 4.Вы также можете скачать это прямо с http://nt4.com/bash/argparser.inc.sh
#!/usr/bin/env bash
# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh
# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com
# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh
unset EXPLODED
declare -a EXPLODED
function explode
{
local c=$#
(( c < 2 )) &&
{
echo function "$0" is missing parameters
return 1
}
local delimiter="$1"
local string="$2"
local limit=${3-99}
local tmp_delim=$'\x07'
local delin=${string//$delimiter/$tmp_delim}
local oldifs="$IFS"
IFS="$tmp_delim"
EXPLODED=($delin)
IFS="$oldifs"
}
# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
if unset -v "$1"; then # Unset & validate varname
if (( $# == 2 )); then
eval $1=\"\$2\" # Return single value
else
eval $1=\(\"\${@:2}\"\) # Return array
fi
fi
}
function decho
{
:
}
function ArgParser::check
{
__args=${#__argparser__arglist[@]}
for (( i=0; i<__args; i++ ))
do
matched=0
explode "|" "${__argparser__arglist[$i]}"
if [ "${#1}" -eq 1 ]
then
if [ "${1}" == "${EXPLODED[0]}" ]
then
decho "Matched $1 with ${EXPLODED[0]}"
matched=1
break
fi
else
if [ "${1}" == "${EXPLODED[1]}" ]
then
decho "Matched $1 with ${EXPLODED[1]}"
matched=1
break
fi
fi
done
(( matched == 0 )) && return 2
# decho "Key $key has default argument of ${EXPLODED[3]}"
if [ "${EXPLODED[3]}" == "false" ]
then
return 0
else
return 1
fi
}
function ArgParser::set
{
key=$3
value="${1:-true}"
declare -g __argpassed__$key="$value"
}
function ArgParser::parse
{
unset __argparser__argv
__argparser__argv=()
# echo parsing: "$@"
while [ -n "$1" ]
do
# echo "Processing $1"
if [ "${1:0:2}" == '--' ]
then
key=${1:2}
value=$2
elif [ "${1:0:1}" == '-' ]
then
key=${1:1} # Strip off leading -
value=$2
else
decho "Not argument or option: '$1'" >& 2
__argparser__argv+=( "$1" )
shift
continue
fi
# parameter=${tmp%%=*} # Extract name.
# value=${tmp##*=} # Extract value.
decho "Key: '$key', value: '$value'"
# eval $parameter=$value
ArgParser::check $key
el=$?
# echo "Check returned $el for $key"
[ $el -eq 2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
[ $el -eq 0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments" >&2 && ArgParser::set true "${EXPLODED[@]}"
[ $el -eq 1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'" >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
shift
done
}
function ArgParser::isset
{
declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
return 1
}
function ArgParser::getArg
{
# This one would be a bit silly, since we can only return non-integer arguments ineffeciently
varname="__argpassed__$1"
echo "${!varname}"
}
##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
local __varname="__argpassed__$1"
local __value="${!__varname}"
test -z "$__value" && return 1
local "$3" && upvar $3 "$__value"
return 0
}
function ArgParser::__construct
{
unset __argparser__arglist
# declare -a __argparser__arglist
}
##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
# check for short arg within long arg
if [[ "$1" =~ \[(.)\] ]]
then
short=${BASH_REMATCH[1]}
long=${1/\[$short\]/$short}
else
long=$1
fi
if [ "${#long}" -eq 1 ]
then
short=$long
long=''
fi
decho short: "$short"
decho long: "$long"
__argparser__arglist+=("$short|$long|$1|$2|$3")
}
##
# @brief show available command line arguments
##
function ArgParser::showArgs
{
# declare -p | grep argparser
printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
printf "Defaults for the options are specified in brackets.\n\n";
__args=${#__argparser__arglist[@]}
for (( i=0; i<__args; i++ ))
do
local shortname=
local fullname=
local default=
local description=
local comma=
explode "|" "${__argparser__arglist[$i]}"
shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide:
fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
test -n "$shortname" \
&& test -n "$fullname" \
&& comma=","
default="${EXPLODED[3]}"
case $default in
false )
default=
;;
"" )
default=
;;
* )
default="[$default]"
esac
description="${EXPLODED[4]}"
printf " %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
done
}
function ArgParser::test
{
# Arguments with a default of 'false' do not take paramaters (note: default
# values are not applied in this release)
ArgParser::addArg "[h]elp" false "This list"
ArgParser::addArg "[q]uiet" false "Supress output"
ArgParser::addArg "[s]leep" 1 "Seconds to sleep"
ArgParser::addArg "v" 1 "Verbose mode"
ArgParser::parse "$@"
ArgParser::isset help && ArgParser::showArgs
ArgParser::isset "quiet" \
&& echo "Quiet!" \
|| echo "Noisy!"
local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
&& echo "Sleep for $__sleep seconds" \
|| echo "No value passed for sleep"
# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"
echo "Remaining command line: ${__argparser__argv[@]}"
}
if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
ArgParser::test "$@"
fi
Если все ваши длинные параметры имеют уникальные и совпадающие первые символы в качестве коротких параметров, например
./slamm --chaos 23 --plenty test -quiet
Такой же как
./slamm -c 23 -p test -q
Вы можете использовать это до getopts переписать $args:
# change long options to short options
for arg; do
[[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
if [ "${arg:0:2}" == "--" ];
then args="${args} -${arg:2:1}"
else args="${args} ${delim}${arg}${delim}"
fi
done
# reset the incoming args
eval set -- $args
# proceed as usual
while getopts ":b:la:h" OPTION; do
.....
Спасибо mtvee за вдохновение ;-)
Встроенный getopts
Только краткие варианты разбора (за исключением KSH93), но вы все равно можете добавить несколько линий сценариев, чтобы сделать Getopts Randles Long Options.
Вот часть кода, найденная в http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts
#== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
#== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
[foo]=f
[bar]=b
[foobar]=F
[barfoo]=B
[help]=h
[man]=h
)
#== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
#== translate long options to short ==#
if [[ "x$OPTION" == "x-" ]]; then
LONG_OPTION=$OPTARG
LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
LONG_OPTIND=-1
[[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
[[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
OPTION=${ARRAY_OPTS[$LONG_OPTION]}
[[ "x$OPTION" = "x" ]] && OPTION="?" OPTARG="-$LONG_OPTION"
if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then
OPTION=":" OPTARG="-$LONG_OPTION"
else
OPTARG="$LONG_OPTARG";
if [[ $LONG_OPTIND -ne -1 ]]; then
[[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
shift $OPTIND
OPTIND=1
fi
fi
fi
fi
#== options follow by another option instead of argument ==#
if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then
OPTARG="$OPTION" OPTION=":"
fi
#== manage options ==#
case "$OPTION" in
f ) foo=1 bar=0 ;;
b ) foo=0 bar=1 ;;
B ) barfoo=${OPTARG} ;;
F ) foobar=1 && foobar_name=${OPTARG} ;;
h ) usagefull && exit 0 ;;
: ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
esac
done
shift $((${OPTIND} - 1))
Вот тест:
# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2
# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2
В противном случае в недавнем Korn Shell кш93, getopts
может естественным образом анализировать длинные параметры и даже отображать справочную страницу.(Видеть http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)
Встроенный в OS X (BSD) getopt не поддерживает длинные параметры, но версия GNU поддерживает: brew install gnu-getopt
.Затем что-то похожее на: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt
.
EasyOptions обрабатывает короткие и длинные параметры:
## Options:
## --verbose, -v Verbose mode
## --logfile=NAME Log filename
source easyoptions || exit
if test -n "${verbose}"; then
echo "log file: ${logfile}"
echo "arguments: ${arguments[@]}"
fi
Простой способ получить только аргументы с длинными именами:
Использовать:
$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""
Скрипт:
#!/bin/bash
function main() {
ARGS=`getArgs "$@"`
a1=`echo "$ARGS" | getNamedArg a1`
a2=`echo "$ARGS" | getNamedArg a2`
a3=`echo "$ARGS" | getNamedArg a3`
a4=`echo "$ARGS" | getNamedArg a4`
a5=`echo "$ARGS" | getNamedArg a5`
a6=`echo "$ARGS" | getNamedArg a6`
a7=`echo "$ARGS" | getNamedArg a7`
echo "a1 = \"$a1\""
echo "a2 = \"$a2\""
echo "a3 = \"$a3\""
echo "a4 = \"$a4\""
echo "a5 = \"$a5\""
echo "a6 = \"$a6\""
echo "a7 = \"$a7\""
exit 0
}
function getArgs() {
for arg in "$@"; do
echo "$arg"
done
}
function getNamedArg() {
ARG_NAME=$1
sed --regexp-extended --quiet --expression="
s/^--$ARG_NAME=(.*)\$/\1/p # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
/^--$ARG_NAME\$/ { # Get arguments in format '--arg value' ou '--arg'
n # - [n]ext, because in this format, if value exists, it will be the next argument
/^--/! p # - If next doesn't starts with '--', it is the value of the actual argument
/^--/ { # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
# Then just repla[c]ed by TRUE
c TRUE
}
}
"
}
main "$@"
если просто так вы хотите вызвать скрипт
myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"
то вы можете воспользоваться этим самым простым способом добиться этого с помощью getopt и --longoptions.
попробуйте это, надеюсь, это будет полезно
# Read command line options
ARGUMENT_LIST=(
"input1"
"input2"
"input3"
)
# read arguments
opts=$(getopt \
--longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
--name "$(basename "$0")" \
--options "" \
-- "$@"
)
echo $opts
eval set --$opts
while true; do
case "$1" in
--input1)
shift
empId=$1
;;
--input2)
shift
fromDate=$1
;;
--input3)
shift
toDate=$1
;;
--)
shift
break
;;
esac
shift
done