getopt, getopts или ручной синтаксический анализ - что использовать, когда я хочу поддерживать как короткие, так и длинные варианты?
В настоящее время я пишу скрипт Bash, который имеет следующие требования:
- он должен работать на самых разных платформах Unix /Linux.
- он должен поддерживать как короткие, так и длинные (GNU) варианты
Я знаю, что getopts
будет предпочтительным способом с точки зрения переносимости, но AFAIK не поддерживает длинные параметры.
getopt
поддерживает длинные опции, но BashGuide настоятельно рекомендует:
Никогда не используйте getopt (1). getopt не может обрабатывать строки с пустыми аргументами или аргументы со встроенным пробелом. Пожалуйста, забудьте, что это когда-либо существовала.
Итак, все еще есть возможность ручного разбора. Это подвержено ошибкам, создает довольно какой-то шаблонный код, и мне нужно обрабатывать ошибки самостоятельно (думаю, getopt(s)
выполняет обработку ошибок сами по себе).
Итак, какой будет предпочтительный выбор в этом случае?
5 ответов
Если он должен быть переносимым в ряд Unices, вам придется придерживаться POSIX sh. И AFAIU там у вас просто нет выбора, кроме как переносить обработку аргументов вручную.
getopt
vs getopts
кажется быть религиозным вопросом. Что касается аргументов против getopt
в FAQ по Bash :
-
"
getopt
не может обрабатывать строки с пустыми аргументами", похоже, ссылается на известную проблему с аргументами optional который выглядит какgetopts
вообще не поддерживается (по крайней мере, от чтенияhelp getopts
для Bash 4.2.24). Изman getopt
:getopt (3) может анализировать длинные параметры с необязательными аргументами, которым предоставляется пустой необязательный аргумент (но не может сделать это для коротких опций). Этот getopt (1) рассматривает необязательные аргументы, которые пусты, как если бы они не присутствовали.
Я не знаю, где «getopt
не может обрабатывать аргументы [...] со встроенными пробелами», но давайте проверьте его:
-
test.sh:
#!/usr/bin/env bash set -o errexit -o noclobber -o nounset -o pipefail params="$(getopt -o ab:c -l alpha,bravo:,charlie --name "$0" -- "[email protected]")" eval set -- "$params" while true do case "$1" in -a|--alpha) echo alpha shift ;; -b|--bravo) echo "bravo=$2" shift 2 ;; -c|--charlie) echo charlie shift ;; --) shift break ;; *) echo "Not implemented: $1" >&2 exit 1 ;; esac done
-
пробег:
$ ./test.sh - $ ./test.sh -acb ' whitespace FTW ' alpha charlie bravo= whitespace FTW $ ./test.sh -ab '' -c alpha bravo= charlie $ ./test.sh --alpha --bravo ' whitespace FTW ' --charlie alpha bravo= whitespace FTW charlie
Похож на проверку и сопряжение со мной, но я уверен, что кто-то покажет, как я полностью неправильно понял предложение. Конечно, проблема переносимости все еще стоит; вам нужно будет решить, сколько времени стоит инвестировать в платформы с более старым или отсутствующим Bash. Мой собственный совет - использовать YAGNI и рекомендации KISS - только для тех конкретных платформ, которые, как вы знаете, будут использоваться. Переносимость кода Shell обычно идет на 100%, поскольку время разработки переходит в бесконечность.
Вот этот getopts_long , написанный как функция оболочки POSIX, которую вы можете встроить в свой скрипт.
Обратите внимание, что Linux getopt
(из util-linux
) работает корректно, если не находится в традиционном режиме и поддерживает длинные параметры, но, вероятно, это не вариант для вас, если вам нужно быть переносимым в другие Unices.
Последние версии ksh93 (getopts
) и zsh (zparseopts
) имеют встроенную поддержку для синтаксического анализа длинных параметров, которые могут быть для вас доступными, поскольку они доступны для большинства Unices (хотя часто они не установлены по умолчанию).
Другой вариант - использовать perl
и его Getopt::Long
, оба из которых должны быть доступны в большинстве Unices в настоящее время, либо путем написания всего скрипта в perl
, либо просто вызвать perl просто для того, чтобы проанализировать этот параметр и передать извлеченную информацию в оболочку. Что-то вроде:
parsed_ops=$(
perl -MGetopt::Long -le '
@options = (
"foo=s", "bar", "neg!"
);
Getopt::Long::Configure "bundling";
$q="'\''";
GetOptions(@options) or exit 1;
for (map /(\w+)/, @options) {
eval "\$o=\$opt_$_";
$o =~ s/$q/$q\\$q$q/g;
print "opt_$_=$q$o$q"
}' -- "[email protected]"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...
Смотрите perldoc Getopt::Long
, что он может сделать и как он отличается от других парсеров параметров.
В каждом обсуждении этого вопроса указывается на возможность вручную писать код разбора - только тогда вы можете быть уверены в функциональности и переносимости. Я советую вам не писать код, который вы можете сгенерировать и повторно создать с помощью простых в использовании генераторов кода с открытым кодом. Используйте Argbash , который был разработан для обеспечения окончательного ответа на вашу проблему. Это хорошо документированный генератор кода, доступный как приложение командной строки , онлайн или как изображение Docker .
Я советую против библиотек bash, некоторые из них используют getopt
(что делает совершенно неспортивным), и это боль, чтобы связать гигантский нечитаемый shell blob с вашим скриптом.
Вы можете использовать getopt
для систем, которые его поддерживают, и использовать откат для систем, которые этого не делают.
Например pure-getopt
реализован в чистом Bash, чтобы быть заменой для GNU getopt
.