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 :

  • " getoptне може обробити порожні рядки аргументів", схоже, посилається на відому проблему з необов'язковими аргументами, яка, схоже, 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" -- "$@")"
    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. У ньому є застарілі параметри, які вимикають описаний вище синтаксис, зокрема, на сторінці вказується "GETOPT_COMPATIBLE примусовий прийом для використання першого формату виклику, визначеного в SYNOPSIS". Однак якщо ви можете розраховувати, що цільові системи встановлять цей пакет, це повністю шлях, оскільки оригінал getopt жахливий, а getopt Баша дуже обмежений
n.caillou

1
Посилання OP, яке стверджує, що "Традиційні версії getopt не можуть обробляти порожні рядки аргументів або аргументи із вбудованим пробілом". і що не слід використовувати util-linux версію getopt не має доказів і більше не є точною. Швидке опитування (5+ років пізніше) показує, що версія getopt за замовчуванням, доступна в ArchLinux, SUSE, Ubuntu, RedHat, Fedora та CentOS (а також у більшості похідних варіантів), всі підтримують необов'язкові аргументи та аргументи з пробілом.
mtalexan

10

Ось цей 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"
    }' -- "$@"
) || exit
eval "$parsed_ops"
# and then use $opt_foo, $opt_bar...

Подивіться, perldoc Getopt::Longщо він може робити і чим він відрізняється від інших варіантів аналізу.


2

Кожна дискусія з цього питання висвітлює можливість написати код розбору вручну - лише тоді ви можете бути впевнені у функціональності та портативності. Я раджу вам не писати код, який ви можете створити та відтворити за допомогою простих у використанні генераторів коду з відкритим кодом. Використовуйте Argbash , який був розроблений, щоб дати остаточну відповідь на вашу проблему. Це добре задокументований генератор коду, доступний як додаток командного рядка , онлайн або як зображення Докера .

Я раджу проти бібліотек bash, деякі з них використовують getopt(що робить цілком нерепортажним), і болісно зв’язати з вашим сценарієм гігантську нечитабельну оболонку оболонки.


0

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

Наприклад pure-getopt, реалізовано в чистому Bash, щоб замінити GNU getopt.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.