getopt, getopts lub parsowanie ręczne - z czego korzystać, gdy chcę obsługiwać zarówno krótkie, jak i długie opcje?

32

Obecnie piszę skrypt Bash, który ma następujące wymagania:

  • powinien działać na wielu różnych platformach Unix / Linux
  • powinien obsługiwać zarówno opcje krótkie, jak i (GNU)

Wiem, że getoptsbyłby to preferowany sposób pod względem przenośności, ale AFAIK nie obsługuje długich opcji.

getoptobsługuje długie opcje, ale BashGuide zdecydowanie zaleca:

Nigdy nie używaj getopt (1). getopt nie może obsługiwać pustych ciągów argumentów ani argumentów z osadzonymi białymi spacjami. Proszę zapomnij, że kiedykolwiek istniał.

Tak więc nadal istnieje możliwość ręcznego parsowania. Jest to podatne na błędy, generuje całkiem sporo kodu typu „płyta podstawowa” i muszę samodzielnie obsługiwać błędy ( getopt(s)wydaje mi się, że same zajmują się obsługą błędów).

Więc jaki byłby preferowany wybór w tym przypadku?

helpermethod
źródło
powiązane: stackoverflow.com/questions/192249/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Odpowiedzi:

9

Jeśli musi być przenośny na wiele Uniksów, musisz trzymać się POSIX sh. A AFAIU nie ma po prostu innego wyboru, jak ręczne przetwarzanie argumentów.

vonbrand
źródło
25

getoptvs getoptswydaje się być kwestią religijną. Co do argumentów przeciwko getoptw Bash FAQ :

  • getoptnie można obsłużyć ciągów pustych argumentów” wydaje się odnosić do znanego problemu z opcjonalnymi argumentami, który, jak się wydaje, w getoptsogóle nie obsługuje (przynajmniej z czytania help getoptsBash 4.2.24). Od man getopt:

    getopt (3) może analizować długie opcje z opcjonalnymi argumentami, które otrzymują pusty opcjonalny argument (ale nie może tego zrobić dla krótkich opcji). Ten getopt (1) traktuje opcjonalne argumenty, które są puste, tak jakby ich nie było.

Nie wiem, skąd pochodzi „ getoptnie można [...] argumentować za pomocą osadzonych białych znaków”, ale przetestujmy to:

  • 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
  • biegać:

    $ ./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

Wygląda na to, że sprawdzam i współpracuję, ale jestem pewien, że ktoś pokaże, jak całkowicie źle zrozumiałem zdanie. Oczywiście kwestia przenośności nadal obowiązuje; musisz zdecydować, ile czasu warto zainwestować w platformy ze starszą wersją Bash lub bez niej. Moja własna rada to stosowanie wytycznych YAGNI i KISS - rozwijaj tylko dla tych konkretnych platform, o których wiesz, że będą używane. Przenośność kodu powłoki zwykle spada do 100% w miarę upływu czasu programowania.

10b0
źródło
11
OP wspomniał o potrzebie przenośności na wiele platform uniksowych, podczas getoptgdy cytowany tutaj temat jest specyficzny dla Linuksa. Zauważ, że getoptnie jest to częścią bash, nie jest to nawet narzędzie GNU, aw systemie Linux jest dostarczane z pakietem util-linux.
Stéphane Chazelas
2
Większość platform ma getopt, tylko Linux AFAIK jest dostarczany z taką, która obsługuje długie opcje lub spacje w argumentach. Pozostałe obsłużyłyby tylko składnię Systemu V.
Stéphane Chazelas
7
getoptto tradycyjne polecenie pochodzące z Systemu V na długo przed wydaniem Linuksa. getoptnigdy nie był ustandaryzowany. Żaden z POSIX, Unix ani Linux (LSB) nigdy nie ustandaryzował getoptpolecenia. getoptsjest określony we wszystkich trzech, ale bez obsługi długich opcji.
Stéphane Chazelas
1
Dodajmy do tej dyskusji: to nie jest tradycyjne getopt. Jest to smak linux-utils, jak wskazuje @ StéphaneChazelas. Posiada starsze opcje, które wyłączą opisaną powyżej składnię, w szczególności strona man stwierdza „GETOPT_COMPATIBLE zmusza getopt do użycia pierwszego formatu wywołania określonego w SYNOPSIS”. Jeśli jednak można oczekiwać, że systemy docelowe będą miały zainstalowany ten pakiet, jest to całkiem
słuszne
1
Link OP, który twierdzi, że „Tradycyjne wersje getopt nie mogą obsługiwać pustych ciągów argumentów ani argumentów z osadzonymi białymi znakami”. i że nie należy używać wersji get-utilux z getopt nie ma dowodów i nie jest już dokładna. Szybka ankieta (ponad 5 lat później) pokazuje, że domyślna wersja getopt dostępna z ArchLinux, SUSE, Ubuntu, RedHat, Fedora i CentOS (jak również większość wariantów pochodnych) obsługuje opcjonalne argumenty i argumenty z białymi spacjami.
mtalexan
10

Jest taki getopts_long napisany jako funkcja powłoki POSIX, którą możesz osadzić w skrypcie.

Pamiętaj, że Linux getopt(od util-linux) działa poprawnie, gdy nie jest w trybie tradycyjnym i obsługuje długie opcje, ale prawdopodobnie nie jest dla ciebie opcją, jeśli chcesz być przenośny na inne Uniksy.

Najnowsze wersje ksh93 ( getopts) i zsh ( zparseopts) mają wbudowaną obsługę parsowania długich opcji, które mogą być opcją dla Ciebie, ponieważ są one dostępne dla większości Uniksów (choć często nie są instalowane domyślnie).

Inną opcją byłoby użycie perli jego Getopt::Longmoduł, które oba powinny być obecnie dostępne w większości Uniksów, albo przez napisanie całego skryptu, perlalbo po prostu wywołanie perla tylko w celu przeanalizowania opcji i podania wyodrębnionej informacji do powłoki. Coś jak:

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...

Zobacz, perldoc Getopt::Longco może zrobić i czym różni się od innych analizatorów opcji.

Stéphane Chazelas
źródło
2

Każda dyskusja na ten temat uwypukla opcję ręcznego pisania kodu parsującego - tylko wtedy możesz być pewien funkcjonalności i przenośności. Odradzam pisanie kodu, który można wygenerować i ponownie wygenerować za pomocą łatwych w użyciu generatorów kodu open source. Użyj Argbash , który został zaprojektowany, aby zapewnić ostateczną odpowiedź na Twój problem. Jest to dobrze udokumentowany generator kodu dostępny jako aplikacja wiersza poleceń , online lub jako obraz Docker .

getoptOdradzam korzystanie z bibliotek bash, niektóre z nich używają (co sprawia, że ​​nie można ich przenosić), a dołączenie do skryptu gigantycznego, nieczytelnego obiektu blob powłoki jest bardzo trudne.

bubla
źródło
0

Możesz użyć getoptw systemach, które go obsługują i skorzystać z rezerwowych systemów, które nie obsługują.

Na przykład pure-getoptjest zaimplementowany w czystym Bashu, aby zastępować GNU getopt.

Potherca
źródło