getopt, getopts или ручной синтаксический анализ - что использовать, когда я хочу поддерживать как короткие, так и длинные опции?


32

В настоящее время я пишу скрипт Bash, который имеет следующие требования:

  • он должен работать на самых разных платформах Unix / Linux
  • он должен поддерживать как короткие, так и (GNU) длинные опции

Я знаю, что это getoptsбыло бы предпочтительным способом с точки зрения мобильности, но AFAIK это не поддерживает длинные варианты.

getoptподдерживает длинные опции, но BashGuide настоятельно рекомендует:

Никогда не используйте getopt (1). getopt не может обрабатывать пустые строки аргументов или аргументы со встроенным пробелом. Пожалуйста, забудьте, что это когда-либо существовало.

Таким образом, есть возможность ручного разбора. Это подвержено ошибкам, производит довольно много стандартного кода, и мне нужно обрабатывать ошибки самостоятельно (я думаю, что getopt(s)делать обработку ошибок самостоятельно).

Итак, что будет предпочтительным выбором в этом случае?


Ответы:


9

Если он должен быть переносимым на ряд Unices, вам придется придерживаться POSIX sh. И AFAIU там у вас просто нет выбора, кроме как вручную обрабатывать аргументы.


25

getoptпротив, getoptsкажется, религиозная проблема. Что касается аргументов против getoptв Bash FAQ :

  • « getoptне может обрабатывать пустые строки аргументов», по-видимому, относится к известной проблеме с необязательными аргументами, которая, по- видимому, getoptsвообще не поддерживается (по крайней мере из чтения help getoptsBash 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" -- "$@")"
    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 - разрабатывать только для тех конкретных платформ, которые, как вы знаете, будут использоваться. Переносимость кода оболочки обычно достигает 100%, а время разработки уходит в бесконечность.


11
В ОП упоминалось о необходимости переносимости на многие платформы Unix, в то время getoptкак вы цитируете здесь, это зависит от Linux. Обратите внимание, что getoptэто не часть bash, это даже не утилита GNU, а в Linux поставляется с пакетом util-linux.
Стефан Шазелас

2
На большинстве платформ есть getoptтолько Linux AFAIK, который поддерживает длинные опции или пробелы в аргументах. Другие будут поддерживать только синтаксис System V.
Стефан Шазелас

7
getoptэто традиционная команда, пришедшая из System V задолго до того, как Linux был выпущен. getoptникогда не был стандартизирован. Ни один из POSIX, Unix или Linux (LSB) никогда не стандартизировал getoptкоманду. getoptsуказан во всех трех, но без поддержки длинных опций.
Стефан Шазелас

1
Просто чтобы добавить к этому обсуждению: это не традиционное getopt. Это вкус linux-utils, как указано @ StéphaneChazelas. Он имеет устаревшие параметры, которые отключают синтаксис, описанный выше, в частности, состояния man-страницы «GETOPT_COMPATIBLE заставляет getopt использовать первый формат вызова, как указано в SYNOPSIS». Однако, если вы можете ожидать, что на целевых системах будет установлен этот пакет, то это совершенно
верный

1
Ссылка OP, которая утверждает: «Традиционные версии getopt не могут обрабатывать пустые строки аргументов или аргументы со встроенным пробелом». и что версия getopt util-linux не должна использоваться, не имеет доказательств и более не точна. Быстрый опрос (5+ лет спустя) показывает, что версия getopt по умолчанию, доступная в ArchLinux, SUSE, Ubuntu, RedHat, Fedora и CentOS (а также в большинстве производных вариантов), поддерживает необязательные аргументы и аргументы с пробелами.
Мталексан

10

Этот getopts_long записан как функция оболочки POSIX, которую вы можете встроить в свой скрипт.

Обратите внимание, что Linux getopt(from 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"
    }' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...

Посмотрите, perldoc Getopt::Longчто он может сделать и чем он отличается от других парсеров опций.


2

В каждом обсуждении по этому вопросу выделяется возможность писать код синтаксического анализа вручную - только тогда вы можете быть уверены в функциональности и переносимости. Я советую вам не писать код, который вы могли бы сгенерировать и сгенерировать с помощью простых в использовании генераторов кода с открытым исходным кодом. Используйте Argbash , который был разработан, чтобы дать окончательный ответ на вашу проблему. Это хорошо документированный генератор кода, доступный как приложение командной строки , онлайн или как образ Docker .

Я не советую использовать библиотеки bash, некоторые из них используют getopt(что делает их совершенно непереносимыми), и очень сложно объединить гигантский нечитаемый блоб оболочки с вашим скриптом.


0

Вы могли бы использовать getoptв системах, которые поддерживают это, и использовать запасной вариант для систем, которые этого не делают.

Например pure-getopt, реализован в чистом Bash, чтобы быть заменой GNU getopt.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.