Jak parsować opcjonalne argumenty w skrypcie bash, jeśli nie podano żadnej kolejności?


13

Nie wiem, jak dołączyć opcjonalne argumenty / flagi podczas pisania skryptu bash dla następującego programu:

Program wymaga dwóch argumentów:

run_program --flag1 <value> --flag2 <value>

Istnieje jednak kilka opcjonalnych flag:

run_program --flag1 <value> --flag2 <value> --optflag1 <value> --optflag2 <value> --optflag3 <value> --optflag4 <value> --optflag5 <value> 

Chciałbym uruchomić skrypt bash, aby pobierał argumenty użytkownika. Jeśli użytkownicy wprowadzą tylko dwa argumenty w kolejności, będzie to:

#!/bin/sh

run_program --flag1 $1 --flag2 $2

Ale co, jeśli uwzględniony zostanie którykolwiek z opcjonalnych argumentów? Myślałbym, że tak będzie

if [ --optflag1 "$3" ]; then
    run_program --flag1 $1 --flag2 $2 --optflag1 $3
fi

Ale co, jeśli podano 4 USD, ale nie 3 USD?


getoptsjest tym, czego chcesz. Bez tego można użyć pętli z instrukcją switch do wykrycia każdej flagi, opcjonalnie lub nie.
Orion,


@orion Z getopts, musiałbym podać każdą kombinację argumentów? 3 i 4, 3 i 5, 3 i 4 i 5 itd.?
ShanZhengYang

Nie, ustawiasz je tylko, jeśli je dostaniesz, w przeciwnym razie zgłasza, że ​​nie został znaleziony, więc po prostu „dostajesz” każdą opcję w dowolnej kolejności i określasz je w dowolnej kolejności, jeśli w ogóle. Ale po prostu przeczytaj stronę podręcznika użytkownika bash, wszystko tam jest.
Orion,

@orion Przykro mi, ale wciąż nie do końca rozumiem getopts. Powiedzmy, że zmuszam użytkowników do uruchomienia skryptu ze wszystkimi argumentami: run_program.sh VAL VAL FALSE FALSE FALSE FALSE FALSEktóry uruchamia program jako program --flag1 VAL --flag2 VAL. Jeśli uruchomiłeś run_program.sh VAL VAL FALSE 10 FALSE FALSE FALSE, program działałby jako program --flag1 VAL --flag2 VAL --optflag2 10. Jak możesz uzyskać takie zachowanie getopts?
ShanZhengYang

Odpowiedzi:


25

Ten artykuł pokazuje dwa różne sposoby - shiftoraz getopts(i omawia zalety i wady obu podejść).

Ze shiftswoim wyglądem skryptu, $1decyduje, jakie działania podjąć, a następnie wykonuje shift, przesuwając $2się $1, $3aby $2itd

Na przykład:

while :; do
    case $1 in
        -a|--flag1) flag1="SET"            
        ;;
        -b|--flag2) flag2="SET"            
        ;;
        -c|--optflag1) optflag1="SET"            
        ;;
        -d|--optflag2) optflag2="SET"            
        ;;
        -e|--optflag3) optflag3="SET"            
        ;;
        *) break
    esac
    shift
done

Po getoptszdefiniowaniu (krótkich) opcji w whilewyrażeniu:

while getopts abcde opt; do
    case $opt in
        a) flag1="SET"
        ;;
        b) flag2="SET"
        ;;
        c) optflag1="SET"
        ;;
        d) optflag2="SET"
        ;;
        e) optflag3="SET"
        ;;
    esac
done

Oczywiście są to tylko fragmenty kodu i pominąłem sprawdzanie poprawności - sprawdzanie, czy ustawiono obowiązkowe argumenty flag1 i flag2 itd.

Które podejście jest do pewnego stopnia kwestią gustu - jak przenośny ma być twój skrypt, czy możesz żyć tylko z krótkimi (POSIX) opcjami, czy chcesz długimi (GNU) opcjami itp.


Zwykle robię to while (( $# ))zamiast while :;i często rezygnuję z błędu w *sprawie
Jasen

odpowiada to tytuł, ale nie pytanie.
Jasen,

@Jasen Spojrzałem na tę ostatnią noc i nie mogłem przez całe życie zobaczyć, dlaczego. Dziś rano jest znacznie jaśniej (i widziałem też odpowiedź Oriona). Skreślę tę odpowiedź później dzisiaj (chciałem najpierw potwierdzić twój komentarz, i wydawało się to najłatwiejszym sposobem na zrobienie tego).
John N

nie ma problemu, to wciąż dobra odpowiedź, tylko pytanie umknęło jej.
Jasen

1
Uwaga: W przypadku set -o nounsetpierwszego rozwiązania wystąpi błąd, jeśli nie zostanie podany żaden parametr. Poprawka:case ${1:-} in
Raphael,

3

użyj tablicy.

#!/bin/bash

args=( --flag1 "$1" --flag2 "$2" )
[  "x$3" = xFALSE ] ||  args+=( --optflag1 "$3" )
[  "x$4" = xFALSE ] ||  args+=( --optflag2 "$4" )
[  "x$5" = xFALSE ] ||  args+=( --optflag3 "$5" )
[  "x$6" = xFALSE ] ||  args+=( --optflag4 "$6" )
[  "x$7" = xFALSE ] ||  args+=( --optflag5 "$7" )

program_name "${args[@]}"

to poprawnie obsłuży argumenty ze spacjami.

[edytuj] Użyłem z grubsza równoważnej składni, args=( "${args[@]}" --optflag1 "$3" )ale G-Man zaproponował lepszy sposób.


1
Możesz to trochę usprawnić, mówiąc args+=( --optflag1 "$3" ). Być może chciałbyś zobaczyć moją odpowiedź na naszą pracę referencyjną, Implikacje bezpieczeństwa wynikające z niezapisania zmiennej w powłokach bash / POSIX , gdzie omawiam tę technikę (budowanie wiersza poleceń w tablicy przez warunkowe dołączanie opcjonalnych argumentów).
G-Man mówi „Przywróć Monikę”

@ G-Man Dzięki, nie widziałem tego w dokumentacji, wiedząc, [@]że mam wystarczające narzędzie do rozwiązania mojego problemu.
Jasen

1

W skrypcie powłoki argumentami są „$ 1”, „$ 2”, „$ 3” itd. Liczba argumentów to $ #.
Jeśli twój skrypt nie rozpoznaje opcji, możesz pominąć wykrywanie opcji i traktować wszystkie argumenty jako operandy.
Aby rozpoznać opcje, użyj wbudowanego getopts


0

Jeśli twoje opcje wprowadzania są pozycyjne (wiesz, w których miejscach się znajdują), a nie są oznaczone flagami, to po prostu zbuduj linię poleceń. Po prostu przygotuj argumenty poleceń dla wszystkich:

FLAG1="--flag1 $1"
FLAG2="--flag2 $2"
OPTFLAG1=""
OPTFLAG2=""
OPTFLAG3=""
if [ xFALSE != x"$3" ]; then
   OPTFLAG1="--optflag1 $3"
fi
if [ xFALSE != x"$4" ]; then
   OPTFLAG2="--optflag2 $4"
fi
#the same for other flags

#now just run the program
runprogram $FLAG1 $FLAG2 $OPTFLAG1 $OPTFLAG2 $OPTFLAG3

Jeśli parametry nie są określone, odpowiednie ciągi są puste i rozwijają się w nic. Zauważ, że w ostatnim wierszu nie ma cudzysłowów. Jest tak, ponieważ chcesz, aby powłoka podzieliła parametry na słowa (aby podać --flag1i $1jako osobne argumenty dla twojego programu). Oczywiście pójdzie to źle, jeśli oryginalne parametry zawierają spacje. Jeśli to ty go uruchamiasz, możesz to zostawić, ale jeśli jest to ogólny skrypt, może mieć nieoczekiwane zachowanie, jeśli użytkownik wprowadzi coś ze spacjami. Aby sobie z tym poradzić, musisz uczynić kod nieco brzydszym.

xPrefiks w []teście istnieje w przypadku $3lub $4jest pusta. W takim przypadku bash rozwinąłby się [ FALSE != $3 ]w [ FALSE != ]błąd składniowy, więc istnieje inny arbitralny znak, aby się przed tym uchronić. Jest to bardzo powszechny sposób, zobaczysz go w wielu skryptach.

Ustawiłem OPTFLAG1i resztę ""na początek, żeby się upewnić (na wypadek, gdyby coś wcześniej było ustawione), ale jeśli tak naprawdę nie zostały zadeklarowane w środowisku, to nie musisz tego robić.

Kilka dodatkowych uwag:

  • Możesz właściwie otrzymać parametry w ten sam sposób, co runprogramrobi: z flagami. O tym John Nmówi. Tam getoptsstaje się przydatne.
  • Używanie FALSE w tym celu jest nieco niestandardowe i dość długie. Zwykle używa się pojedynczego znaku (ewentualnie -) do zasygnalizowania pustej wartości lub po prostu przekazuje pusty ciąg znaków, na przykład "", jeśli nie jest to poprawna wartość parametru. Pusty ciąg powoduje również, że test jest krótszy, wystarczy użyć if [ -n "$3" ].
  • Jeśli istnieje tylko jedna opcjonalna flaga lub jeśli są one zależne, więc nigdy nie możesz mieć OPTFLAG2, ale nie OPTFLAG1, możesz po prostu pominąć te, których nie chcesz ustawić. Jeśli użyjesz ""pustych parametrów, jak sugerowano powyżej, możesz pominąć wszystkie końcowe opróżnienia.

Ta metoda jest prawie sposobem przekazywania opcji do kompilatorów w Makefiles.

I znowu - jeśli dane wejściowe mogą zawierać spacje, robi się brzydki.


Nie, nie chcesz, aby powłoka dzieliła parametry na słowa. Powinieneś zawsze cytować wszystkie odniesienia do zmiennych powłoki, chyba że masz dobry powód, aby tego nie robić i jesteś pewien, że wiesz, co robisz. Zobacz Implikacje związane z bezpieczeństwem braku cytowania zmiennej w powłokach bash / POSIX , na wypadek, gdybyś nie był jej zaznajomiony, i przewiń w dół do mojej odpowiedzi , gdzie dokładnie rozwiązuję ten problem (przy użyciu tej samej techniki, której używa Jasen ).
G-Man mówi „Przywróć Monikę”
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.