Использование getopts в скрипте оболочки bash для получения длинных и коротких параметров командной строки
Я хочу иметь длинные и короткие формы параметры командной строки вызывается с помощью моего скрипта.
Я знаю, что getopts
можно использовать, но, как и в Perl, я не смог сделать то же самое с shell.
любые идеи о том, как это можно сделать, чтобы я мог использовать такие параметры, как:
./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/
В приведенном выше, обе команды означают одно и то же для моей оболочки, но с помощью getopts
, Я не смог реализовать эти?
29 ответов
The bash getopts
builtin не поддерживает длинные имена опций с префиксом двойной тире. Он поддерживает только односимвольные опции.
есть инструмент оболочки getopt
это другая программа, а не встроенный bash. Реализация GNU getopt(3)
(используется командной строкой getopt(1)
в Linux) поддерживает разбор длинных опций.
но реализация BSD getopt
(например, на Mac OS X) не поддерживает долго опции.
некоторые другие ответы показывают решение для использования bash builtin getopts
для имитации длинных опций. Это решение фактически делает короткий вариант, чей символ "-". Так что вы получите " -- " в качестве флага. Затем все, что следует, становится OPTARG, и вы тестируете OPTARG с вложенным case
.
это умно, но он поставляется с предостережениями:
-
getopts
не удается применить спецификацию opt. Он не может возвращать ошибки, если пользователь предоставляет Недопустимая опция. Вы должны сделать свою собственную проверку ошибок при разборе OPTARG. - OPTARG используется для длинного имени опции, что усложняет использование, когда ваш длинный вариант сам имеет аргумент. Вы хотите, чтобы код, как дополнительный чехол.
таким образом, хотя можно написать больше кода, чтобы обойти отсутствие поддержки длинных опций, это намного больше работы и частично побеждает цель использования синтаксического анализатора getopt для упрощения код.
getopt
и getopts
это разные звери, и люди, кажется, имеют некоторое непонимание того, что они делают. getopts
-это встроенная команда bash
для обработки параметров командной строки в цикле и назначения каждой найденной опции и значения по очереди встроенным переменным, чтобы вы могли их обрабатывать. getopt
, однако, является внешней служебной программой, и это фактически не обрабатывает ваши варианты для вас то, как, например, bash getopts
, Perl Getopt
модуль или Питон 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 (GNU getopt
is по умолчанию в 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 "" in
-v | --verbose ) VERBOSE=true; shift ;;
-d | --debug ) DEBUG=true; shift ;;
-m | --memory ) MEMORY=""; shift 2 ;;
--debugfile ) DEBUGFILE=""; shift 2 ;;
--minheap )
JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio="; shift 2 ;;
--maxheap )
JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio="; 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"
так что вы можете легко обрабатывать их. Цитирование вокруг ""
и ""
важно, поскольку это гарантирует, что аргументы с пробелами в них обрабатываются должным образом.
если вы удалите первые 9 строк (все через eval set
линия), код все еще работают! Однако ваш код будет намного разборчивее в том, какие варианты он принимает: в частности, вам нужно будет указать все параметры в описанной выше "канонической" форме. С использованием getopt
, однако, вы можете сгруппировать однобуквенные параметры, использовать более короткие неоднозначные формы длинных опций, используйте либо --file foo.txt
или --file=foo.txt
стиль, использовать -m 4096
или -m4096
стиль, варианты смешивания и не-варианты в любом заказе, ЕТК. getopt
также выводит сообщение об ошибке, если найдены нераспознанные или неоднозначные параметры.
Примечание: на самом деле их два совершенно разные версии getopt
, basic getopt
и GNU getopt
, С различными функциями и различными соглашениями о вызовах.2 Basic getopt
довольно сломан: он не только не обрабатывает длинные параметры, он также не может даже обрабатывать встроенные пространства внутри аргументов или пустых аргументов, тогда как getopts
делает это право. Приведенный выше код не будет работать в обычном!--3-->. 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
в Path консоли впереди /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).
функция bash builtin getopts может использоваться для анализа длинных опций, помещая символ тире с последующим двоеточием в 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: [-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
checking nor option - разбор аргументов для длинных опций. Фрагмент сценария выше показывает, как это можно сделать вручную. Основной принцип также работает в оболочке Debian Almquist ("dash"). Обратите внимание на особый случай:
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"). The POSIX стандарт говорит, что между ними должно быть пространство, которое в случае "-- longoption " завершит синтаксический анализ опции и превратит все longoptions в аргументы без опции.
встроенный getopts
команда по-прежнему, AFAIK, ограничена только односимвольными параметрами.
есть (или была) внешняя программа getopt
это реорганизовало бы набор опций таким образом, чтобы было легче анализировать. Вы также можете адаптировать этот дизайн для обработки длинных опций. Пример использования:
aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
case "" in
(-a) aflag=yes;;
(-b) bflag=yes;;
(-f) flist="$flist "; shift;;
(--) shift; break;;
(-*) echo ": error - unrecognized option " 1>&2; exit 1;;
(*) break;;
esac
shift
done
# Process remaining non-option arguments
...
вы можете использовать аналогичную схему с .
обратите внимание, что фундаментальная слабость с внешней
вот пример, который фактически использует 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 in
-a|--along) aflag="yes" ;;
-b|--blong) bflag="yes" ;;
# for options with required arguments, an additional shift is required
-c|--clong) cargument="" ; shift;;
(--) shift; break;;
(-*) echo ": error - unrecognized option " 1>&2; exit 1;;
(*) break;;
esac
shift
done
длинные варианты могут быть проанализированы стандартом getopts
builtin как "аргументы" для -
"опции"
это портативный и родной оболочке POSIX – не требуется никаких внешних программ или bashisms являются.
это руководство реализует длинные параметры в качестве аргументов для , Так что --alpha
видит getopts
as -
С аргументом 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
иметь дополнительно
посмотри 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 "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
# say hello
echo "Hello, ${FLAGS_name}!"
для ОС с расширенным getopt, который поддерживает длинные параметры (например, Linux), вы можете do:
$ ./hello_world.sh --name Kate
Hello, Kate!
для остальных, вы должны использовать короткий вариант:
$ ./hello_world.sh -n Kate
Hello, Kate!
добавление нового флага так же просто, как добавление нового DEFINE_ call
.
используя getopts
С короткими / длинными опциями и аргументами
работает со всеми комбинациями, e.Г.:
- foobar-f --bar
- foobar --foo-b
- foobar-bf -- bar --foobar
- foobar-fbFBAshorty --bar-FB -- arguments=longhorn
- foobar-fA "text shorty" - B -- arguments= "text longhorn"
- bash foobar-F -- barfoo
- sh foobar-B --foobar - ...
- Баш ./ 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 these 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 in
# Normal option processing
-h | --help)
# usage and help
;;
-v | --version)
# version info
;;
# ...
# Special cases
--)
break
;;
--*)
# error unknown (long) option
;;
-?)
# error unknown (short) option
;;
# FUN STUFF HERE:
# Split apart combined short options
-*)
split=
shift
set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
continue
;;
# Done with options
*)
break
;;
esac
# for testing purposes:
echo ""
shift
done
конечно, тогда вы не можете использовать длинные варианты стиля с одним тире. И если вы хотите добавить сокращенные версии (например, -- verbos вместо --verbose), вам нужно добавить их вручную.
но если вы хотите сделать getopts
функциональность наряду с длинными опциями, это простой способ сделать это.
Я также помещаю этот фрагмент в суть.
встроенный getopts
Не могу этого сделать. Существует внешний getopt(1) программа, которая может это сделать, но вы получаете ее только в Linux из util-linux пакета. Он поставляется с примером скрипта getopt-parse.Баш.
также 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 "" ]; do
case in
"--dlong")
shift
if [ "${1:1:0}" != "-" ]
then
echo "==> dlong "
shift
fi;;
*) echo "==> other "; shift;;
esac
done
exit
на ksh93
, getopts
поддерживает длинные имена...
while getopts "f(file):s(server):" flag
do
echo "$flag" $OPTIND $OPTARG
done
или так сказали учебники, которые я нашел. Попробуй и увидишь.
изобретение еще одной версии колеса...
эта функция является (надеюсь) POSIX-совместимой простой заменой оболочки bourne для GNU getopt. Он поддерживает короткие / длинные опции, которые могут принимать обязательные/необязательные/нет аргументов, и способ, которым указаны опции, почти идентичен GNU getopt, поэтому преобразование тривиально.
конечно, это все еще значительный кусок кода для ввода в скрипт, но это около половины строк известного getopt_long shell функция, и может быть предпочтительнее в случаях, когда вы просто хотите заменить существующие 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 after displaying error message .
exiterr () {
printf %s\n "" >&2
exit
}
# 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=""
longopts=""
shift 2
getopt=
nonopt=
while [ $# -gt 0 ]; do
opt=
arg=
argtype=
case "" 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 "" in
( -* ) exiterr 1 " requires an argument";;
( ?* ) arg=""; shift 2;;
( * ) exiterr 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 ";;
esac
};;
# process long option
( --?* ) { # --xarg[=foo]
suffix=${1#*=} # foo (unless there was no =)
if [ "${suffix}" = "" ]; 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 "" in
( -* ) exiterr 1 \
"--${optword} requires an argument";;
( ?* ) arg=""; 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 ";;
esac
};;
# any other parameters starting with -
( -* ) exiterr 1 "Unknown option ";;
# remember non-option parameters
( * ) nonopt="${nonopt}$(save "")"; 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 )" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
#eval set -- ${opts}
eval "set -- ${opts}"
while [ $# -gt 0 ]; do
case "" in
( -- ) shift; break;;
( -h|--help ) help=1; shift; break;;
( -v|--version ) version_help=1; shift; break;;
( -d|--directory ) dir=; shift 2;;
( -c|--client ) useclient=1; client=; shift 2;;
( -s|--server ) startserver=1; server_name=; shift 2;;
( -L|--load ) load=; shift 2;;
( -D|--delete ) delete=1; shift;;
esac
done
else
shorthelp=1 # getopt returned (and reported) an error.
fi
здесь вы можете найти несколько различных подходов для анализа сложных опций в 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: [--loglevel[=]<value>]" >&2
exit 2
;;
esac
break; done
done
# End of file
Я только пишу сценарии оболочки время от времени и выпадают из практики, поэтому любая обратная связь приветствуется.
используя стратегию, предложенную @Arvid Requate, мы заметили некоторые ошибки пользователя. Пользователь, который забывает включить значение, случайно будет иметь имя следующего параметра, рассматриваемое как значение:
./getopts_test.sh --loglevel= --toc=TRUE
приведет к тому, что значение " loglevel "будет рассматриваться как"--toc=TRUE". Это может следует избегать.
я адаптировал некоторые идеи о проверке ошибки пользователя для CLI от http://mwiki.wooledge.org/BashFAQ/035 обсуждение ручного парсинга. Я вставил проверку ошибок в обработку аргументов " - "и" -".
затем я начал возиться с синтаксисом, поэтому любые ошибки здесь строго моя вина, а не авторов.
мой подход помогает пользователям, которые предпочитают вводить длинные С или без знака равенства. То есть он должен иметь тот же ответ на "--loglevel 9", что и "--loglevel=9". В методе -- / space это невозможно точно знать, если пользователь забывает аргумент, поэтому требуется некоторое угадывание.
- если пользователь имеет формат знака long/equal (--opt=), то пробел после = вызывает ошибку, потому что аргумент не был предоставлен.
- если у пользователя есть аргументы long/space (--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
здесь пример вывода ошибки проверки на user 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's answer, we have
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!
die() {
printf '%s\n' "" >&2
exit 1
}
printparse(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'Parse: %s%s%s\n' "" "" "" >&2;
fi
}
showme(){
if [ ${VERBOSE} -gt 0 ]; then
printf 'VERBOSE: %s\n' "" >&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: [-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: [-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"
Я работаю над этим вопросом довольно долго... и сделал свою собственную библиотеку, которую вам нужно будет исходить в вашем основном скрипте. См.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 "" in
--)
# [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
EndOpt=1 ;;&
--version) ((EndOpt)) && args[$i]="" || args[$i]="-V";;
# default case : short option use the first char of the long option:
--?*) ((EndOpt)) && args[$i]="" || args[$i]="-${1:2:1}";;
# pass through anything else:
*) args[$i]="" ;;
esac
shift
done
# reset the translated args
set -- "${args[@]}"
function usage {
echo "Usage: [options] files" >&2
exit
}
# 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))
[[ "" == "--" ]] && 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
Я хотел что-то без внешних зависимостей, со строгой поддержкой bash (- u), и мне нужно было работать даже с более старыми версиями bash. Это обрабатывает различные типы параметров:
- короткие bools (- h)
- короткие параметры (- I " изображение.jpg")
- длинные булы (--help)
- equals options (--file= " filename.ext")
- параметры пространства (--file " filename.ext")
- concatinated bools (- 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 ; do
local variants=${param//\|/ }
for variant in $variants ; do
if [[ "$variant" = "" ]] ; 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 ]] ; 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 "" ]]; then
local nodash="${2#-}"
if [ "$nodash" = "" ]; then
# The next argument has NO preceding dash so it is a value
value=""
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' "" >&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 $*
У меня еще недостаточно репутации, чтобы прокомментировать или проголосовать за его решение, но МСП работал очень хорошо для меня. Единственная проблема, с которой я столкнулся, заключалась в том, что аргументы заканчиваются одинарными кавычками (поэтому у меня есть их полоса).
Я также добавил некоторые примеры использования и текст справки. Я включу свою слегка расширенную версию здесь:
#!/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 in
-a|--along) aflag=true ;;
-b|--blong) bflag=true ;;
# for options with required arguments, an additional shift is required
-c|--clong) cargument="" ; shift;;
-h|--help|-\?) echo -e $HELP_TEXT; exit;;
(--) shift; break;;
(-*) echo ": error - unrecognized option " 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=
shift
if [[ $aflag == true ]]; then
echo a is true
fi
done
чтобы оставаться кросс-платформенной совместимой и избегать зависимости от внешних исполняемых файлов, я портировал некоторый код с другого языка.
Я нахожу его очень простым в использовании, вот пример:
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 "" is missing parameters
return 1
}
local delimiter=""
local string=""
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 "" && upvar "value(s)"
upvar() {
if unset -v ""; then # Unset & validate varname
if (( $# == 2 )); then
eval =\"$2\" # Return single value
else
eval =\(\"${@: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 [ "" == "${EXPLODED[0]}" ]
then
decho "Matched with ${EXPLODED[0]}"
matched=1
break
fi
else
if [ "" == "${EXPLODED[1]}" ]
then
decho "Matched 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=
value="${1:-true}"
declare -g __argpassed__$key="$value"
}
function ArgParser::parse
{
unset __argparser__argv
__argparser__argv=()
# echo parsing: "$@"
while [ -n "" ]
do
# echo "Processing "
if [ "${1:0:2}" == '--' ]
then
key=${1:2}
value=
elif [ "${1:0:1}" == '-' ]
then
key=${1:1} # Strip off leading -
value=
else
decho "Not argument or option: ''" >& 2
__argparser__argv+=( "" )
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 ''" >&2 # && __argparser__argv+=( "" )
[ $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 && ArgParser::set "" "${EXPLODED[@]}" && shift
shift
done
}
function ArgParser::isset
{
declare -p "__argpassed__" > /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__"
echo "${!varname}"
}
##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
local __varname="__argpassed__"
local __value="${!__varname}"
test -z "$__value" && return 1
local "" && upvar "$__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 [[ "" =~ \[(.)\] ]]
then
short=${BASH_REMATCH[1]}
long=${1/\[$short\]/$short}
else
long=
fi
if [ "${#long}" -eq 1 ]
then
short=$long
long=''
fi
decho short: "$short"
decho long: "$long"
__argparser__arglist+=("$short|$long|||")
}
##
# @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 "" )" == "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 для вдохновения ;-)
Builtin getopts
только разбирать короткие варианты (кроме ksh93),
но вы все равно можете добавить несколько строк сценариев, чтобы getopts обрабатывал длинные параметры.
вот часть кода, найденная в 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
смогите естественно проанализировать длинные варианты и даже показать а мужская страница. (См. 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
getopts "может использоваться" для разбора длинных опций, если вы не ожидаете, что у них будут аргументы...
вот как:
$ cat > longopt
while getopts 'e:-:' OPT; do
case $OPT in
e) echo echo: $OPTARG;;
-) #long option
case $OPTARG in
long-option) echo long option;;
*) echo long option: $OPTARG;;
esac;;
esac
done
$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test
Если вы попытаетесь использовать OPTIND для получения параметра для опции long, getopts будет рассматривать его как первый необязательный позиционный параметр и прекратит разбор любых других параметров. В таком случае вам будет лучше обрабатывать его вручную с помощью простого оператора case.
это "всегда" работа:
$ cat >longopt2
while (($#)); do
OPT=
shift
case $OPT in
--*) case ${OPT:2} in
long1) echo long1 option;;
complex) echo comples with argument ; shift;;
esac;;
-*) case ${OPT:1} in
a) echo short option a;;
b) echo short option b with parameter ; shift;;
esac;;
esac
done
$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test
хотя это не так гибко, как getopts, и вам нужно сделать большую часть кода проверки ошибок самостоятельно в экземплярах case...
но это вариант.
тю.
не очень довольны чистыми вариантами bash. почему бы не использовать perl, чтобы получить то, что вы хотите. Непосредственно проанализируйте массив $* и автоматически назовите свои параметры.
простой вспомогательный скрипт:
#!/usr/bin/perl
use Getopt::Long;
my $optstring = shift;
my @opts = split(m#,#, $optstring);
my %opt;
GetOptions(\%opt, @opts);
print "set -- " . join(' ', map("'$_'", @ARGV)) . ";";
my $xx;
my $key;
foreach $key (keys(%opt))
{
print "export $key='$opt{$key}'; ";
}
тогда вы можете использовать в своем скрипте как один лайнер, например:
#!/bin/bash
eval `getopts.pl reuse:s,long_opt:s,hello $*`;
echo "HELLO: $hello"
echo "LONG_OPT: $long_opt"
echo "REUSE: $reuse"
echo $*
/tmp/script.sh hello --reuse me --long_opt whatever_you_want_except_spaces --hello 1 2 3
Привет: 1 LONG_OPT: whatever_you_want_except пространства ПОВТОРНОЕ ИСПОЛЬЗОВАНИЕ: мне
1 2 3
единственный нюанс здесь пробелы не работают. Но он избегает довольно сложного синтаксиса цикла bash, работает с длинными args, автоматически называет их переменными и автоматически изменяет размер$*, поэтому будет работать 99% времени.