Jak korzystać z getopt w linii poleceń bash z tylko długimi opcjami?

13

W getoptlinii poleceń bash znajduje się polecenie. getoptmoże być używany z krótkimi opcjami (np. getopt -o axby "$@") i może być używany zarówno z krótkimi, jak i długimi opcjami (np. getopt -o axby -l long-key -- "$@"), ale teraz potrzebuję tylko długich opcji (tj. krótkie opcje w ogóle nie istnieją), jednak polecenie getopt -l long-key -- "$@"nie --long-keypoprawnie przeanalizuj opcję. Więc w jaki sposób można użyć getoptpolecenia z zaledwie długich opcji? Czy jest to niemożliwe, czy to tylko błąd getoptpolecenia?

Zwycięzca
źródło
Tagujesz wewnętrzny getopts, ale używasz /usr/bin/getoptpolecenia.
Anthon
@Anthon Przepraszamy, użyłem niewłaściwego tagu, ale nie mam wystarczającej reputacji, aby dodać kolejny tag, który wymaga 300 reputacji. Jednak właśnie usunąłem zły tag.
Victor

Odpowiedzi:

15

getoptjest całkowicie w porządku, bez krótkich opcji. Ale musisz powiedzieć, że nie masz krótkich opcji. To dziwactwo w składni - z instrukcji:

Jeśli w pierwszej części nie zostanie znaleziona żadna opcja -olub --optionsopcja, pierwszy parametr drugiej części zostanie użyty jako krótki ciąg opcji.

Oto, co dzieje się w teście: getopt -l long-key -- --long-key footraktuje --long-keyjako listę opcji -egklnoyi foojako jedyny argument. Posługiwać się

getopt -o '' -l long-key -- "$@"

na przykład

$ getopt -l long-key -o '' -- --long-key foo
 --long-key -- 'foo'
$ getopt -l long-key -o '' -- --long-key --not-recognized -n foo
getopt: unrecognized option '--not-recognized'
getopt: invalid option -- 'n'
 --long-key -- 'foo'
Gilles „SO- przestań być zły”
źródło
Czy OP pomieszał getoptsi getoptzarażał twoją odpowiedź? Zaczynasz od komentowania, getoptsa potem tylko wspomnienia getopt.
Anthon
@Anthon Cała moja odpowiedź dotyczy getoptprogramu z GNU coreutils, o to właśnie chodzi. Naprawiłem tekst, który powiedział getopts. Dzięki. getoptsnawet nie robi długich opcji, więc żadne z nich nie miałoby zastosowania getopts.
Gilles „SO- przestań być zły”
OP pierwotnie miał getoptstag. Nie chciałem zmieniać twojej odpowiedzi, ponieważ zwykle wiesz znacznie lepiej ode mnie, o czym piszesz :-)
Anthon
Straciłem prawie godzinę, próbując to rozgryźć. Właśnie oszczędziłeś mi kilka próżnych łyków kawy. Dzięki ... teraz lepiej wykorzystajmy tę kawę. ☕️
dmmd
1

Nie wiem, getoptale getoptswbudowanego można używać do obsługi tylko długich opcji takich jak ten:

while getopts :-: o
do  case "$o$OPTARG" in
(-longopt1) process ;;
(-longopt2) process ;;
esac; done

Oczywiście, jak to jest, to nie działa, jeśli długie opcje mają argumenty. Można to jednak zrobić, ale jak nauczyłem się nad tym pracować. Chociaż początkowo umieściłem go tutaj, zdałem sobie sprawę, że w przypadku długich opcji nie ma wiele użyteczności. W tym przypadku skracałem moje case (match)pola tylko o jedną, przewidywalną postać. Teraz wiem, że doskonale nadaje się do krótkich opcji - jest najbardziej przydatny, gdy zapętla ciąg o nieznanej długości i wybiera pojedyncze bajty zgodnie z ciągiem opcji. Ale gdy opcją jest argument, niewiele można zrobić z for var do case $var inkombinacją, którą mógłby zrobić. Myślę, że lepiej jest, aby było to proste.

Podejrzewam, że to samo dotyczy prawdy, getoptale nie wiem o niej wystarczająco dużo, aby powiedzieć z całą pewnością. Biorąc pod uwagę poniższą tablicę arg, zademonstruję mój własny parser arg - który zależy przede wszystkim od relacji ewaluacja / przypisanie, którą doceniam aliasi $((shell=math)).

set -- this is ignored by default --lopt1 -s 'some '\'' 
args' here --ignored   and these are ignored \
--alsoignored andthis --lopt2 'and 

some "`more' --lopt1 and just a few more

To ciąg arg, z którym będę pracował. Teraz:

aopts() { env - sh -s -- "$@"
} <<OPTCASE 3<<\OPTSCRIPT
acase() case "\$a" in $(fmt='
        (%s) f=%s; aset "?$(($f)):";;\n'
        for a do case "$a" in (--) break;;
        (--*[!_[:alnum:]]*) continue;;
        (--*) printf "$fmt" "$a" "${a#--}";;
        esac;done;printf "$fmt" '--*' ignored)
        (*) aset "" "\$a";;esac
shift "$((SHIFT$$))"; f=ignored; exec <&3 
OPTCASE
aset()  {  alias "$f=$(($f${1:-=$(($f))+}1))"
        [ -n "${2+?}" ] && alias "${f}_$(($f))=$2"; }
for a do acase; done; alias
#END
OPTSCRIPT

Przetwarza tablicę arg na jeden z dwóch różnych sposobów, w zależności od tego, czy podajesz jej jeden czy dwa zestawy argumentów oddzielone --separatorem. W obu przypadkach dotyczy to sekwencji przetwarzania do tablicy arg.

Jeśli nazwiesz to tak:

: $((SHIFT$$=3)); aopts --lopt1 --lopt2 -- "$@"

Jego pierwszym zadaniem będzie napisanie acase()funkcji, która będzie wyglądać następująco:

acase() case "$a" in 
    (--lopt1) f=lopt1; aset "?$(($f)):";;
    (--lopt2) f=lopt2; aset "?$(($f)):";;
    (--*) f=ignored; aset "?$(($f)):";;
    (*) aset "" "$a";;esac

I obok shift 3. Podstawienie polecenia w acase()definicji funkcji jest oceniane, gdy powłoka wywołująca buduje dane wejściowe funkcji tutaj, ale acase()nigdy nie jest wywoływana ani definiowana w powłoce wywołującej. Jest on oczywiście wywoływany w podpowłoce, dzięki czemu można dynamicznie określać interesujące opcje w wierszu poleceń.

Jeśli podasz mu tablicę bez rozdzielaczy, po prostu zapełni acase()się ona dopasowaniami dla wszystkich argumentów rozpoczynających się od łańcucha --.

Funkcja praktycznie całe przetwarzanie odbywa się w podpowłoce - iteracyjnie zapisuje każdą wartość arg do aliasów przypisanych nazwami asocjacyjnymi. Po przejściu wypisuje każdą zapisaną wartość alias- która jest określona przez POSIX, aby wydrukować wszystkie zapisane wartości cytowane w taki sposób, aby ich wartości mogły zostać ponownie wprowadzone do powłoki. Więc kiedy to zrobię ...

aopts --lopt1 --lopt2 -- "$@"

Jego dane wyjściowe wyglądają następująco:

...ignored...
lopt1='8'
lopt1_1='-s'
lopt1_2='some '\'' args'
lopt1_3='here'
lopt1_4='and'
lopt1_5='just'
lopt1_6='a'
lopt1_7='few'
lopt1_8='more'
lopt2='1'
lopt2_1='and

some "`more'

Przechodząc przez listę argumentów, sprawdza zgodność bloku bloku z przypadkiem. Jeśli znajdzie tam dopasowanie, rzuca flagę f=optname. Dopóki nie znajdzie ponownie prawidłowej opcji, doda każdy kolejny argument do tablicy, którą buduje na podstawie bieżącej flagi. Jeśli ta sama opcja zostanie podana wiele razy, wyniki się złożą i nie będą zastępować. Wszystko, co nie jest w przypadku - lub jakiekolwiek argumenty następujące po ignorowanych opcjach - są przypisywane do ignorowanej tablicy.

Dane wyjściowe są chronione przez powłokę do automatycznego wprowadzania przez powłokę przez powłokę, a zatem:

eval "$(: $((SHIFT$$=3));aopts --lopt1 --lopt2 -- "$@")"

... powinno być całkowicie bezpieczne. Jeśli z jakiegoś powodu nie jest to bezpieczne, prawdopodobnie powinieneś zgłosić raport o błędzie do opiekuna powłoki.

Przypisuje dwa rodzaje wartości aliasów dla każdego dopasowania. Po pierwsze, ustawia flagę - dzieje się tak niezależnie od tego, czy opcja poprzedza niepasujące argumenty. Tak więc każde wystąpienie --flagna liście argumentów zostanie uruchomione flag=1. To się nie komplikuje - --flag --flag --flagpo prostu dostaje flag=1. Wartość ta jednak zwiększa się - dla wszelkich argumentów, które mogą za nią następować. Może być używany jako klucz indeksu. Po wykonaniu evalpowyższych czynności mogę:

printf %s\\n "$lopt1" "$lopt2"

... żeby dostać ...

8
1

A więc:

for o in lopt1 lopt2
do list= i=0; echo "$o = $(($o))"
        while [ "$((i=$i+1))" -le "$(($o))" ]
        do list="$list $o $i \"\${${o}_$i}\" "
done; eval "printf '%s[%02d] = %s\n' $list";  done

WYNIK

lopt1 = 8
lopt1[01] = -s
lopt1[02] = some ' args
lopt1[03] = here
lopt1[04] = and
lopt1[05] = just
lopt1[06] = a
lopt1[07] = few
lopt1[08] = more
lopt2 = 1
lopt2[01] = and

some "`more

I do argumentów, które nie pasują, zastąpiłbym ignorowane w powyższym for ... inpolu, aby uzyskać:

ignored = 10
ignored[01] = this
ignored[02] = is
ignored[03] = ignored
ignored[04] = by
ignored[05] = default
ignored[06] = and
ignored[07] = these
ignored[08] = are
ignored[09] = ignored
ignored[10] = andthis
mikeserv
źródło