Использование getopts для обработки длинных и коротких параметров командной строки
Я хочу иметь длинные и короткие формы параметров командной строки, вызванных с помощью моей оболочки script.
Я знаю, что getopts
можно использовать, но, как и в Perl, я не смог сделать то же самое с оболочкой.
Любые идеи о том, как это можно сделать, чтобы я мог использовать такие параметры, как:
./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/
В приведенном выше примере обе команды означают одно и то же для моей оболочки, но используя getopts
, я не смог их реализовать?
Ответы
Ответ 1
Есть три реализации, которые можно рассмотреть:
-
Баш встроил getopts
. Это не поддерживает длинные имена параметров с префиксом двойной тире. Он поддерживает только односимвольные параметры.
-
Реализация BSD UNIX автономной команды getopt
(именно это использует MacOS). Это также не поддерживает длинные варианты.
-
GNU реализация автономного getopt
. GNU getopt(3)
(используется в командной строке getopt(1)
в Linux) поддерживает парсинг длинных опций.
Некоторые другие ответы показывают решение для использования встроенных в bash getopts
для имитации длинных опций. Это решение фактически делает короткую опцию, чей символ "-". Таким образом, вы получаете "-" в качестве флага. Затем все последующее становится OPTARG, и вы проверяете OPTARG с вложенным case
.
Это умно, но с оговорками:
-
getopts
не может применить опцию спецификации. Он не может возвращать ошибки, если пользователь указывает неверную опцию. Вы должны сделать свою собственную проверку ошибок, когда вы анализируете OPTARG. - OPTARG используется для длинного имени опции, что усложняет использование, когда у вашей длинной опции есть аргумент. Вы заканчиваете тем, что должны были кодировать это непосредственно как дополнительный случай.
Поэтому, хотя можно написать больше кода, чтобы обойти отсутствие поддержки длинных опций, это намного больше работы и частично сводит на нет цель использования парсера getopt для упрощения вашего кода.
Ответ 2
getopt
и getopts
- разные звери, и люди, похоже, немного недопонимают, что они делают. getopts
- это встроенная команда bash
для обработки параметров командной строки в цикле и назначения каждой найденной опции и значения, в свою очередь, для встроенных переменных, поэтому вы можете их обработать. getopt
, однако, является внешней утилитой, и на самом деле она не обрабатывает ваши варианты, как, например, bash getopts
, модуль Perl getopt
или модули Python optparse
/argparse
. Все, что делает getopt
, - это canonicalize опций, которые передаются - то есть конвертируют их в более стандартную форму, так что проще для оболочки script обрабатывать их. Например, приложение getopt
может преобразовать следующее:
myscript -ab infile.txt -ooutfile.txt
в это:
myscript -a -b -o outfile.txt infile.txt
Вы должны сами выполнить обработку. Вам не нужно использовать getopt
вообще, если вы делаете различные ограничения на способ указания параметров:
- только один аргумент аргумента;
- все параметры идут до любых позиционных параметров (т.е. аргументов без опций);
- для параметров со значениями (например,
-o
выше), значение должно идти как отдельный аргумент (после пробела).
Зачем использовать getopt
вместо getopts
? Основная причина заключается в том, что только GNU getopt
предоставляет вам поддержку многоязычных параметров командной строки. 1 (GNU getopt
по умолчанию используется для Linux. Mac OS X и FreeBSD поставляются с базовым и не очень полезно getopt
, но версия GNU может быть установлена, см. ниже.)
Например, здесь пример использования GNU getopt
, из script моего имени 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' -- "[email protected]"`
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
, basic getopt
и GNU getopt
, с различными функциями и различными соглашениями о вызовах. 2 Basic getopt
: Он не только не обрабатывает длинные параметры, но также не может обрабатывать встроенные пространства внутри аргументов или пустые аргументы, тогда как getopts
делает это правильно. Вышеприведенный код не будет работать в базовом getopt
. GNU 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).
Ответ 3
Функция встроенного 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
После копирования в исполняемый файл name= 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
или парсинг опционных аргументов для длинных опций. Фрагмент script выше показывает, как это можно сделать вручную. Основной принцип также работает в оболочке 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 filename" ), который будет объединен с опцией (как в "-ffilename" ). В стандарте POSIX указывается, что между ними должно быть пространство, которое в случае "- longoption" прекратит разбор параметров и перевернет все longoptions в аргументы без опций.
Ответ 4
Встроенная команда getopts
по-прежнему, AFAIK, ограничена только односимвольными опциями.
Существует (или используется) внешняя программа getopt
, которая будет реорганизовывать набор параметров таким образом, чтобы было легче анализировать. Вы также можете адаптировать этот дизайн для обработки длинных параметров. Пример использования:
aflag=no
bflag=no
flist=""
set -- $(getopt abf: "[email protected]")
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
превосходит, хотя и ограничен тем, что он обрабатывает только однобуквенные параметры.
Ответ 5
Вот пример, который на самом деле использует 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: -- "[email protected]")
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
Ответ 6
Длинные опции могут быть проанализированы стандартными встроенными getopts
как "аргументы" для -
"option"
Это переносимая и нативная оболочка POSIX - никаких внешних программ или ошибок не требуется.
Это руководство реализует длинные варианты, как аргументы -
опции, так --alpha
рассматривается getopts
, как -
с аргументом alpha
и --bravo=foo
рассматривается как -
с аргументом bravo=foo
. Истинный аргумент может быть ${OPTARG#*=}
с помощью простой замены: ${OPTARG#*=}
.
В этом примере -b
(и его --bravo
форма, --bravo
) имеет обязательную опцию (обратите внимание на ручную реконструкцию принудительного применения для длинной формы). Неолеановые опции -b для длинных аргументов следуют после знака равенства, например --bravo=foo
(разделители пробела для длинных опций будет трудно реализовать).
Поскольку здесь используются getopts
, это решение поддерживает использование, например cmd -ac --bravo=foo -d FILE
(который объединяет опции -a
и - c
и -a
длинные опции со стандартными параметрами), в то время как большинство других ответов здесь либо борются, либо не в состоянии сделать это.
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 [email protected] list
Когда аргумент является тире (-
), он имеет еще два компонента: имя флага и (необязательно) его аргумент. Я разграничиваю их стандартным способом, которым будет любая команда, с первым знаком равенства (=
). $LONG_OPTARG
- это просто содержимое $OPTARG
без имени флага или знака равенства.
Внутренний case
реализует длинные параметры вручную, поэтому он требует некоторой обработки:
-
bravo=?
соответствует --bravo=foo
но не --bravo=
(примечание: case
останавливается после первого совпадения) - следует
bravo*
, отмечая отсутствующий обязательный аргумент в --bravo
и --bravo=
-
alpha* | charlie*
alpha* | charlie*
ловит аргументы, данные опциям, которые их не поддерживают -
''
присутствует для поддержки не опций, которые начинаются с тире -
*
перехватывает все другие длинные опции и воссоздает ошибку, выданную getopts для недопустимой опции
Вам не обязательно нужны все эти предметы домашнего обихода. Например, возможно, вы хотите, чтобы --bravo
имел необязательный аргумент (который -b
не может поддерживать из-за ограничения в getopts
). Просто удалить =?
и связанный случай сбоя, а затем вызовите ${ARG_B:=$DEFAULT_ARG_B}
при первом использовании $ARG_B
.
Чтобы принимать длинные параметры с аргументами с пробелом -d, вам понадобится (достаточно безопасный) 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}"
, но eval
необходим, чтобы быть полностью переносимым POSIX. $OPTIND
- это число, ссылающееся на следующий аргумент оболочки, скажем, $2
(что означает $1
--bravo
). Тогда eval
будет интерпретировать ARG_B="$2"
и, поскольку он заключен в кавычки, он защищен от злоупотреблений (я не смог найти способ обмануть его и сделать что-то неуместное).
Гарантировать непустое значение нетривиально и требует его фактической проверки, поэтому в этом случае мы получаем условную ошибку и генерируем фатальную ошибку. Если вы позволите ему быть пустым, вам нужно будет условно $OPTIND
или запустить бесконечный цикл. [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))
должно сделать.
Последняя часть этого дополнительного предложения увеличивает $OPTIND
для правильного поглощения аргумента опции и перехода к следующей опции.
Ответ 7
Взгляните на shFlags, который представляет собой переносимую библиотеку оболочки (это означает: sh, bash, dash, ksh, zsh в Linux, Solaris и т.д.).
Это добавляет новые флаги так же просто, как добавление одной строки в ваш скрипт, и она обеспечивает автоматическую функцию использования.
Вот простой Hello, world!
используя shFlag:
#!/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 "[email protected]" || 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
.
Ответ 8
Использование getopts
с короткими/длинными опциями и аргументами
Работает со всеми комбинациями, например:
- foobar -f - -B ар
- foobar - -f oO -B
- foobar -bf - -B ar - -f oobar
- foobar -f bFBAshorty - -B ar -f B - arguments = longhorn
- foobar -f Текстовый коротышка -B --arguments = "текстовый лонгхорн"
- Баш Фубар -f - -B arfoo
- sh foobar -B - -f oobar -...
- bash./foobar -f - -B ar
Некоторые объявления для этого примера
[email protected]
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 ######
[email protected]
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
Ответ 9
Другой способ...
# 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
Ответ 10
Я так решил:
# A string with command options
[email protected]
# 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
настолько запутывают.
Ответ 11
Если вам не нужна зависимость 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') "[email protected]"
continue
;;
# Done with options
*)
break
;;
esac
# for testing purposes:
echo "$1"
shift
done
Конечно, тогда вы не можете использовать длинные параметры стиля с одним тире. И если вы хотите добавить сокращенные версии (например, --verbos вместо --verbose), вам нужно добавить их вручную.
Но если вы хотите получить getopts
функциональность вместе с длинными опциями, это простой способ сделать это.
Я также добавил этот фрагмент в gist.
Ответ 12
Встроенный getopts
не может этого сделать. Существует внешняя программа getopt (1), которая может это сделать, но вы получаете ее только в Linux из пакета util-linux. Он поставляется с примером script getopt-parse.bash.
Существует также getopts_long
, записанный как функция оболочки.
Ответ 13
#!/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]==> [email protected]"
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
Ответ 14
В ksh93
, getopts
поддерживает длинные имена...
while getopts "f(file):s(server):" flag
do
echo "$flag" $OPTIND $OPTARG
done
Или, как я узнал, у меня есть учебники. Попробуйте и посмотрите.
Ответ 15
Изобретая еще одну версию колеса...
Эта функция является (надеюсь) POSIX-совместимой заменой оболочки bourne для GNU getopt. Он поддерживает короткие/длинные параметры, которые могут принимать обязательные/необязательные/отсутствующие аргументы, а способ указания параметров почти идентичен GNU getopt, поэтому преобразование тривиально.
Конечно, это по-прежнему значительная часть кода, чтобы перейти на script, но это примерно половина строк известной функции оболочки 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 "[email protected]") # 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" -- "[email protected]")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "[email protected]")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "[email protected]"
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 "[email protected]")"
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 "[email protected]")"
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)" -- "[email protected]")
opts=$(posix_getopt "$shortopts" "$longopts" "[email protected]")
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
Ответ 16
Я только пишу сценарии оболочки время от времени и выпадает из практики, поэтому любые отзывы приветствуются.
Используя стратегию, предложенную @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 = значение" и "--opt значение". При знаке равенства аргумент командной строки отображается как "opt = value", а работа, выполняемая при разборе строки, отделяется при "=". Напротив, со значением "--opt" имя аргумента "opt", и у нас есть задача получить следующее значение, предоставленное в командной строке. Тот, где @Arvid Requate использовал $ {! OPTIND}, косвенную ссылку. Я до сих пор не понимаю этого, ну, вообще, и комментарии в BashFAQ, кажется, предостерегают против этого стиля (http://mywiki.wooledge.org/BashFAQ/006). Кстати, я не думаю, что предыдущие комментарии автора о важности OPTIND = $ (($ OPTIND + 1)) верны. Я имею в виду, я не вижу вреда, если его опустить.
В самой новой версии этого сценария флаг -v означает распечатку VERBOSE.
Сохраните его в файле с именем "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
Вот пример выходных данных проверки ошибок в пользовательском intpu
$ ./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 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"
Ответ 17
Здесь вы можете найти несколько различных подходов к комплексному анализу параметров в 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
Ответ 18
Я работаю над этой темой довольно долго... и создал свою собственную библиотеку, которую вам нужно будет найти в главном script.
Для примера см. libopt4shell и cd2mpc.
Надеюсь, это поможет!
Ответ 19
Улучшенное решение:
# 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
Ответ 20
Может быть, проще использовать 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
Ответ 21
Я хотел что-то без внешних зависимостей, со строгой поддержкой bash (-u), и мне нужно было работать даже в более старых версиях bash. Это обрабатывает различные типы параметров:
- short bools (-h)
- короткие варианты (-i "image.jpg" )
- long bools (--help)
- equals options (--file = "filename.ext" )
- space options (--file "filename.ext" )
- concatinated bools (-hvm)
Просто вставьте следующее в начало script:
# 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 $*
Ответ 22
У меня пока нет достаточного количества комментариев, чтобы прокомментировать или проголосовать за его решение, но sme answer работал очень хорошо для меня. Единственная проблема, с которой я столкнулся, заключалась в том, что аргументы заканчиваются одиночными кавычками (поэтому у меня их нет).
Я также добавил некоторые примеры использования и текст HELP. Я включу мою небольшую расширенную версию здесь:
#!/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: -- "[email protected]")
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
Ответ 23
Принятый ответ делает очень хорошую работу, указывая на все недостатки встроенных в 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
Ответ 24
Если все ваши длинные параметры имеют уникальные и соответствующие первые символы в виде коротких опций, например
./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 за вдохновение; -)
Ответ 25
Чтобы оставаться межплатформенной совместимостью и избегать зависимости от внешних исполняемых файлов, я портировал код с другого языка.
Мне очень удобно пользоваться, вот пример:
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 "[email protected]"
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: "[email protected]"
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 "[email protected]"
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 "[email protected]"
fi
Ответ 26
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
Ответ 27
Встроенный getopts
только разбор коротких опций (кроме ksh93),
но вы все равно можете добавить несколько строк сценариев, чтобы makeopts обрабатывал длинные параметры.
Вот часть кода, найденного в 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 ksh93, getopts
может, естественно, анализировать длинные параметры и даже отображать страницу man. (См. http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options)
Ответ 28
Th встроенная ОС X (BSD) getopt не поддерживает длинные параметры, но версия GNU делает: brew install gnu-getopt
. Тогда что-то похожее на: cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt
.
Ответ 29
Простой DIY, чтобы получить только длинные именованные аргументы:
Использование:
$ ./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 "[email protected]"'
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 "[email protected]"; 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 "[email protected]"
Ответ 30
если просто так вы хотите вызвать скрипт
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 "" \
-- "[email protected]"
)
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