Sprawdź, czy ustawiono wiele zmiennych

19

Chciałbym się upewnić, że w pewnym momencie skryptu, po sourcewczytaniu pliku konfiguracyjnego, ustawionych jest kilka zmiennych, a jeśli tak nie jest, zatrzymanie wykonywania, informowanie użytkownika o brakującej zmiennej. próbowałem

for var in $one $two $three ; do
    ...

ale jeśli na przykład $twonie jest ustawiony, pętla nigdy nie zostanie wykonana $two. Następną rzeczą, jakiej próbowałem, było

for var in one two three ; do
    if [ -n ${!var} ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Ale jeśli dwa nie są ustawione, nadal otrzymuję „dwa są ustawione na” zamiast „dwa nie są ustawione”.

Jak mogę się upewnić, że wszystkie wymagane zmienne są ustawione?

Aktualizacja / rozwiązanie: Wiem, że istnieje różnica między „zestawem” a „zestawem, ale pustym”. Teraz używam (dzięki /programming//a/16753536/3456281 i odpowiedzi na to pytanie):

if [ -n "${!var:-}" ] ; then

więc jeśli varjest ustawione, ale puste, nadal jest uważane za nieprawidłowe.

Jaspis
źródło
1
Możesz także dodać set -una początku skryptu, aby zakończyć go natychmiast po użyciu zmiennej nieustalonej.
n.

Odpowiedzi:

8

Błąd cytowania.

if [ -n "${!var}" ] ; then

Na przyszłość: ustawienie

set -x

przed uruchomieniem kodu pokazałby ci problem. Zamiast dodawać to do kodu, możesz wywołać swój skrypt za pomocą

bash -vx ./my/script.sh
Hauke ​​Laging
źródło
To działa, ale co się dzieje z / bez cytatów? Czy moje ogólne podejście jest prawidłowe na pierwszym miejscu?
Jasper
Tak, to jest przedogniskowe: odpowiedziałem na pytanie, zanim zostało zadane ... 8-)
Hauke ​​Laging
4
@Jasper Powinieneś zawsze cytować zmienne. Nie kosztuje to więcej czasu niż zastanawianie się, czy jest to konieczne za każdym razem. Ale pozwala uniknąć błędów.
Hauke ​​Laging
Przeciwnie, cytowanie chroni tylko przed ekspansją. Jeśli interesuje Cię rozszerzenie, cytowanie z pewnością nie jest dobrym rozwiązaniem. Na przykład: cs = 673,290,765; set - $ (IFS =,; echo $ cs); echo $ #; wyjście: 3
mikeserv
2
@ mikeserv Tylko pojedyncze cytaty chronią przed ekspansją, czego oczywiście nie sugeruję. Podwójne cudzysłowy chronią przed podziałem słów. Nie zaprzeczyłem, że mogą istnieć przypadki, w których cytowanie musi zostać pominięte. Ale to nie jest argument przeciwko mojej ogólnej zasadzie. Jakiego rodzaju ludzie będą używać czegoś takiego set -- $(IFS=, ; echo $cs)? Ludzie, którzy muszą zapytać, dlaczego if [ -n ${!var} ] ; thennie działa? Prawdopodobnie nie.
Hauke ​​Laging
5

Jedyne, czego potrzebujesz, to cytaty w teście:

for var in one two three ; do
    if [ -n "${!var}" ] ; then
        echo "$var is set to ${!var}"
    else
        echo "$var is not set"
    fi
done

Pracuje dla mnie.

TNW
źródło
4

Jeśli chcesz zatrzymać program:

N= 
${one?var 1 is unset} 
${two:?var 2 is unset or null}
${three:+${N:?var 3 is set and not null}}

To załatwi sprawę. Każda wiadomość po znaku zapytania jest drukowana stderri skorupa rodzica umiera. Cóż, OK, więc nie każda wiadomość - tylko jedna - tylko pierwsza, która się nie powiedzie, drukuje wiadomość, ponieważ powłoka umiera. Lubię używać tych testów w ten sposób:

( for v in "$one" "$two" "$three" ; do
    i=$((i+1)) ; : ${v:?var $i is unset or null...} 
done ) || _handle_it

Miałem dużo więcej do powiedzenia na ten temat tutaj .

mikeserv
źródło
2

Możesz dodać

set -u

na początku skryptu, aby zakończył się, gdy próbuje użyć zmiennej nieustawionej.

Skrypt jak

#!/bin/sh
set -u
echo $foo

spowoduje

script.sh: 3: script.sh: foo: parametr nie został ustawiony

Jeśli bashzamiast tego używasz , błąd będzie wyglądał następująco:

script.sh: linia 3: foo: zmienna niezwiązana

n.st
źródło
A Twoim zdaniem ma to sens w przypadku kontroli w czasie wykonywania?
Hauke ​​Laging
2
@HaukeLaging Nie podążam za tobą - set -uzapobiega dokładnie temu rodzajowi błędu, którego OP próbuje uniknąć i (w przeciwieństwie do wszystkich innych rozwiązań) nie ogranicza się do określonego zestawu zmiennych. W rzeczywistości jest użytecznym środkiem ostrożności dla prawie wszystkich skryptów powłoki, aby bezpiecznie zawiodły, zamiast robić nieoczekiwane rzeczy, gdy zmienna nie jest ustawiona. Ten artykuł stanowi przydatne odniesienie do tematu.
n.st
@ n.st Nie zgadzam się - wartość null może być tak samo przydatna, jak wartość null, jeśli ją planujesz.
mikeserv
1
@ n.st Nie ma to sensu dla OP, ponieważ nie chce ochrony przed dostępem do zmiennych nieustalonych (BTW: ustawiona, ale pusta zmienna spowodowałaby ten sam błąd, ale nie zareagowałaby na twoją „ochronę”). Chce, aby w czasie wykonywania sprawdzić, czy zmienna jest rozbrojona / pusta. Twoja sugestia może być przydatna jako ogólna pomoc rozwojowa, ale nie rozwiązuje problemu PO.
Hauke ​​Laging
set -umyślę, że można go również użyć, ale nie jest tak elastyczny, jak poprawne rozszerzenie parametrów (zobacz moje zaktualizowane pytanie). Przy pomocy set -umusiałbym uzyskać dostęp do wszystkich zmiennych docelowych, jeśli chcę sprawdzić ich obecność w jednym miejscu.
Jasper
2

Rozwiązanie, które jest maksymalnie przyjazne, testuje wszystkie wymagania i zgłasza je razem, zamiast zawodzić przy pierwszym i wymagając w jedną i drugą stronę, aby wszystko działało poprawnie:

#!/bin/bash

required_vars=(one two three)

missing_vars=()
for i in "${required_vars[@]}"
do
    test -n "${!i:+y}" || missing_vars+=("$i")
done
if [ ${#missing_vars[@]} -ne 0 ]
then
    echo "The following variables are not set, but should be:" >&2
    printf ' %q\n' "${missing_vars[@]}" >&2
    exit 1
fi

Używam zmiennej tablicowej do śledzenia, które zmienne nie zostały ustawione, i wykorzystuję wynik do napisania komunikatu skierowanego do użytkownika.

Uwagi:

  • Zacytowałem ${required_vars[@]}wfor pętli głównie z przyzwyczajenia - nie radziłbym umieszczać żadnych metaznaków powłoki w nazwach zmiennych!
  • Nie zacytowałem ${#missing_vars[@]} , ponieważ zawsze jest to liczba całkowita, nawet jeśli jesteś wystarczająco przewrotny, aby zignorować poprzednią radę.
  • Użyłem %qpodczas drukowania;%sbyłoby zwykle wystarczające.
  • Wyjście błędu zawsze trafia do strumienia błędów wraz z >&2 , więc nie są przekazywane do dalszych poleceń
  • Cisza jest złotem - nie drukuj informacji o postępach ani informacji debugowania, chyba że zostaniesz o to poproszony. To sprawia, że ​​błędy stają się bardziej oczywiste.
Toby Speight
źródło
1

bash4.2 pozwala ci sprawdzić, czy zmienna jest ustawiona z -voperatorem; nieustawiona zmienna i zmienna ustawiona na pusty ciąg znaków to dwa różne warunki:

$ unset foo
$ [[ -v foo ]] && echo foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty
$ foo=
$ [[ -v foo ]] && echo foo is set
foo is set
$ [[ -z "$foo" ]] && echo foo is empty
foo is empty
chepner
źródło
Pytałem o „wiele” zmiennych, więc szukałem czegoś takiego jak pośrednia ...
Jasper
Nie trzeba zadnie, ponieważ -vbierze nazwę zmiennej, tak for var in one two three; [[ -v $var ]] && ...; doneby sprawdzić, czy każda one, twoi threesą ustawione w kolejności.
chepner
1

Myślę, że jeśli masz na myśli not set, więc zmiennej nigdy nie wolno inicjować. Jeśli użyjesz [ -n "${!var}" ], więc pusta zmienna jak nie two=""będzie działać, gdy jest ustawiona . Możesz spróbować:

one=1
three=3

for var in one two three; do
  declare -p $var > /dev/null 2>&1 \
  && printf '%s is set to %s\n' "$var" "${!var}" \
  || printf '%s is not set\n' "$var"
done
Cuonglm
źródło
0

Zwięzłe (choć nieco zhackowane) rozwiązanie, aby obsłużyć przypadek, w którym sourceskrypt d ustawia zmienne na wartość zerową, to inicjuje zmienne do jakiejś specjalnej wartości przed pozyskaniem skryptu, a następnie sprawdza tę wartość później np

one=#UNSET#
two=#UNSET#
three=#UNSET#

. set_vars_script

if [[ $one$two$three == *#UNSET#* ]] ; then
  echo "One or more essential variables are unset" >&2
  exit 1
fi
Laurence Renshaw
źródło
1
inne fajne rozwiązanie, ale chciałbym podać bardziej konkretny komunikat o błędzie niż „jedna lub więcej zmiennych nieuzbrojona” ...
Jasper
0

Rozszerzyłem odpowiedź @ mikeserv .

Ta wersja pobiera listę zmiennych w celu sprawdzenia obecności, wypisuje nazwę brakującej zmiennej i wyzwala wywołanie funkcji use () w przypadku błędu.

REQD_VALUES=("VARIABLE" "FOO" "BAR" "OTHER_VARIABLE")
( i=0; for var_name in ${REQD_VALUES[@]}; do
    VALUE=${!var_name} ;
    i=$((i+1)) ; : ${VALUE:?$var_name is missing}
done ) || usage

Przykładowe dane wyjściowe, gdy brakuje wartości:

./myscript.sh: line 42: VALUE: OTHER_VARIABLE is missing

Zauważ, że możesz zmienić zmienną o nazwie VALUE na alternatywną nazwę, która bardziej odpowiada twojemu pożądanemu wynikowi.

Ryan
źródło