Я анализирую параметры, getopts
но хотел бы также обрабатывать длинные параметры.
print-args ()
{
title="$1" ; shift
printf "\n%s\n" "${title}: \$@:"
for arg in "$@"; do
(( i = i + 1 ))
printf "%s |%s|\n" "${i}." "$arg"
done
}
getopts_test ()
{
aggr=()
for arg in "$@"; do
case $arg in
("--colour"|"--color") aggr+=( "-c" ) ;;
("--colour="*|"--color="*) aggr+=( "-c" "${arg#*=}" ) ;;
(*) aggr+=( "$arg" ) ;;
esac
done
print-args "print" "$@"
eval set -- "${aggr[@]}"
print-args "eval" "$@"
set -- "${aggr[@]}"
print-args "set" "$@"
local OPTIND OPTARG
local shortopts="C:"
while getopts "$shortopts" arg; do
case $arg in
("c") context="$OPTARG" ;;
(*) break ;;
esac
done
shift $(( OPTIND - 1 ))
}
set -- "${aggr[@]}"
Но мне интересно , правильно ли используется .
Или более уместен следующий вариант (использование eval
)?
eval set -- "${aggr[@]}"
Я выполнил тест, показанный ниже. С eval строка "Gunga Din" разделяется, тогда как с set -- "${aggr[@]}"
, она корректно анализируется как одна строка.
getopts_test -f -g 130 --colour="170 20" "Gunga Din"
print: $@:
1. |-f|
2. |-g|
3. |130|
4. |--colour=170 20|
5. |Gunga Din|
eval: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170|
6. |20|
7. |Gunga|
8. |Din|
set: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170 20|
6. |Gunga Din|
Затем я запустил другую функцию, которая использует не-GNU getopt
.
getopt_test ()
{
shortopts="Vuhv::H::w::e::n::l::C:"
shortopts="${shortopts}bgcrmo"
longopts="version,usage,help,verbosity::"
longopts="${longopts},heading::,warning::,error::"
longopts="${longopts},blu,grn,cyn,red,mgn,org"
opts=$( getopt -o "$shortopts" -l "$longopts" -n "${0##*/}" -- "$@" )
print-args "\$@:" "$@"
print-args "opts:" "$opts"
set -- "$opts"
print-args "set -- \"$opts\"" "$@"
eval set -- "$opts"
print-args "eval set -- \"$opts\"" "$@"
}
Это привело к следующему
getopt_test --warning=3 "foo'bar" "Gunga Din"
$@:
1. |--warning=3|
2. |foo'bar|
3. |Gunga Din|
opts:
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|
set -- "$opts"
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|
eval set -- "$opts"
1. |--warning|
2. |3|
3. |--|
4. |foo'bar|
5. |Gunga Din|
Как показано, результатом getopt является одна запись с переупорядоченными позиционными аргументами. Это показывает необходимость использования eval set -- "$opts"
для разделения позиционных аргументов в opts
строке на пять записей для разбора и обработки опций.
решение1
Идея заключается в предварительной обработке аргументов и изменении каждого --context
на -C
который getopts
затем можно обработать? Я полагаю, что это сработает, но учтите, что длинные параметры в стиле GNU также могут принимать аргументы в формате --context=foobar
, а ваша конструкция здесь не поддерживает это. Пользователь должен знать, что этот конкретный инструмент здесь требует --context
foobar
два отдельных аргумента. Или вам нужно будет сделать предварительную обработку более сложной.
Вы также можете проверить все аргументы, которые начинаются с --
, так как в противном случае, например, опечатка --cotnext
будет отправлена getopts
как есть, и вы получите жалобы на неизвестные параметры. (Или, что еще хуже, будут включены неправильные параметры.)
set -- "${aggr[@]}"
Но мне интересно , правильно ли используется .Или более уместен следующий вариант (с использованием eval)?
set -- "${aggr[@]}"
расширяет элементы массива до отдельных слов, а затем назначает эти слова позиционным параметрам. Каждый элемент массива станет ровно одним позиционным параметром, без изменений.
eval set -- "${aggr[@]}"
будет расширять все элементы массива, затем объединять их вместе пробелами, добавлять set --
и оценивать результат как команду оболочки. То есть, если у вас есть элементы массива abc def
, $(date >&2)
, ghi'jkl
, команда будет
set -- abc def $(date >&2) ghi'jkl
что в итоге приведет к получению abc
и def
как двух отдельных параметров, и выведет дату на stderr, за исключением того, что одинарная кавычка вызовет синтаксическую ошибку.
Использование eval
будет уместным, если у вас есть что-то, предназначенное для создания выходных данных, которые указаны для ввода в оболочку.
Если вы используете Linux (и вас не волнует переносимость), вы можете сделать то, что roaima предложил в комментариях, и использовать версию util-linux getopt
(без s
). Она также поддерживает длинные параметры, есть ответы, показывающие, как использовать ее вgetopt, getopts или ручной парсинг — что использовать, если я хочу поддерживать как короткие, так и длинные параметры?И вэтот ТАК ответа такжемой ответ здесь.
Кстати, с этим getopt
выбыиспользуйте eval
, так как в качестве команды она ограничена созданием только одной строки в качестве вывода, а не списка, как массив, поэтому она использует кавычки оболочки, чтобы обойти эту проблему.
решение2
Вы можете разобрать --foo
длинные опции в стиле -style с помощью getopts
встроенной функции, добавив -
в качестве короткой опции аргумент в optstring и извлекая фактическую длинную опцию из $OPTARG
. Простой пример:
while getopts :sc:-: o; do
case $o in
:) echo >&2 "option -$OPTARG needs an argument"; continue;;
'?') echo >&2 "unknown option -$OPTARG"; continue;;
-) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; OPTARG=${OPTARG#=};;
esac
echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"
который затем можно использовать как script -c foo
или script --context=foo
.
Если вы также хотите, чтобы длинные опции проверялись так же, как и короткие, и также принимали сокращенные формы, вам нужно что-то более сложное. Нет особой мудрости в том, чтобы переусложнять плохой скрипт оболочки, как этот, но если вам нужен пример, вот он:
short_opts=sc:
long_opts=silent/ch/context:/check/co # those who take an arg END with :
# override via command line for testing purposes
# if [ "$#" -ge 2 ]; then
# short_opts=$1; long_opts=$2; shift 2
# fi
while getopts ":$short_opts-:" o; do
case $o in
:) echo >&2 "option -$OPTARG needs an argument" ;continue;;
'?') echo >&2 "bad option -$OPTARG" ;continue;;
-) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; lo=/$long_opts/
case $lo in
*"/$o"[!/:]*"/$o"[!/:]*) echo >&2 "ambiguous option --$o"; continue;;
*"/$o"[:/]*) ;;
*) o=$o${lo#*"/$o"}; o=${o%%[/:]*} ;;
esac
case $lo in
*"/$o/"*) OPTARG= ;;
*"/$o:/"*)
case $OPTARG in
'='*) OPTARG=${OPTARG#=};;
*) eval "OPTARG=\$$OPTIND"
if [ "$OPTIND" -le "$#" ] && [ "$OPTARG" != -- ]; then
OPTIND=$((OPTIND + 1))
else
echo >&2 "option --$o needs an argument"; continue
fi;;
esac;;
*) echo >&2 "unknown option --$o"; continue;;
esac
esac
echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"
затем
$ ./script --context=33
OPT context=33
$ ./script --con=33
OPT context=33
$ ./script --co
OPT co=
$ ./script --context
option --context needs an argument