Jak sprawdzić, czy zmienna jest w ogóle zdefiniowana w Bash przed wersją 4.2 z opcją powłoki rzeczownika?

16

Czy dla wersji Bash wcześniejszych niż „GNU bash, wersja 4.2” istnieją równoważne alternatywy dla -vopcji testpolecenia? Na przykład:

shopt -os nounset
test -v foobar && echo foo || echo bar
# Output: bar
foobar=
test -v foobar && echo foo || echo bar
# Output: foo
Tim Friske
źródło
-vnie jest opcją test, ale jest operatorem wyrażeń warunkowych.
Tim
@Tim To trzy rzeczy, przy czym token, ciąg i część linii: AN optiondo polecenia test -v, AN operatorDo conditional expressioni unary test primarydla [ ]. Nie mieszaj języka angielskiego z definicjami powłoki.

Odpowiedzi:

36

Przenośny dla wszystkich powłok POSIX:

if [ -n "${foobar+1}" ]; then
  echo "foobar is defined"
else
  echo "foobar is not defined"
fi

Zrób to, ${foobar:+1}jeśli chcesz traktować w foobarten sam sposób, czy jest pusty, czy nie zdefiniowany. Możesz także użyć, ${foobar-}aby uzyskać pusty ciąg znaków, gdy foobarjest niezdefiniowany, i wartość w foobarprzeciwnym razie (lub wstawić dowolną inną wartość domyślną po -).

W ksh, jeśli foobarjest zadeklarowane, ale nie zdefiniowane, jak w typeset -a foobar, to ${foobar+1}rozwija się do pustego ciągu.

Zsh nie ma zmiennych, które zostały zadeklarowane, ale nie zostały ustawione: typeset -a foobartworzy pustą tablicę.

W bash tablice zachowują się w inny i zaskakujący sposób. ${a+1}rozwija się tylko do 1jeśli ajest niepustą tablicą, np

typeset -a a; echo ${a+1}    # prints nothing
e=(); echo ${e+1}            # prints nothing!
f=(''); echo ${f+1}          # prints 1

Ta sama zasada dotyczy tablic asocjacyjnych: zmienne tablicowe są traktowane jak zdefiniowane, jeśli mają niepusty zbiór indeksów.

Innym, specyficznym dla basha sposobem testowania, czy zmienna dowolnego typu została zdefiniowana, jest sprawdzenie, czy jest wymieniona w . Raportuje puste tablice zgodnie z definicją, w przeciwieństwie do${!PREFIX*}${foobar+1} , ale zgłasza zmienne zadeklarowane, ale nieprzypisane ( unset foobar; typeset -a foobar) jako niezdefiniowane.

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is defined";;
  *) echo "foobar is not defined";;
esac

Jest to równoważne z testowaniem wartości zwracanejtypeset -p foobar lubdeclare -p foobar , z tym wyjątkiem, że typeset -p foobarzawodzi w przypadku zadeklarowanych, ale nieprzypisanych zmiennych.

W bash, podobnie jak w ksh, set -o nounset; typeset -a foobar; echo $foobarwyzwala błąd przy próbie rozwinięcia niezdefiniowanej zmiennej foobar. W przeciwieństwie do ksh set -o nounset; foobar=(); echo $foobar(lub echo "${foobar[@]}") również powoduje błąd.

Zauważ, że we wszystkich opisanych tutaj sytuacjach, ${foobar+1}rozwija się do pustego ciągu, jeśli i tylko jeśli $foobarspowodowałby błąd pod set -o nounset.

Gilles „SO- przestań być zły”
źródło
1
Co z tablicami? W wersji Bash „GNU bash, wersja 4.1.10 (4) -release (i686-pc-cygwin)” echo "${foobar:+1}"nie drukuje, 1jeśli declare -a foobarzostało wcześniej wydane, a zatem foobarjest tablicą indeksowaną. declare -p foobarpoprawnie raportuje declare -a foobar='()'. Czy działa "${foobar:+1}"tylko w przypadku zmiennych innych niż tablica?
Tim Friske
@TimFriske ${foobar+1}(bez :, odwróciłem dwa przykłady w mojej oryginalnej odpowiedzi) jest poprawny dla tablic w bash, jeśli twoja definicja „zdefiniowanego” brzmi „ $foobardziałałaby set -o nounset”. Jeśli twoja definicja jest inna, bash jest trochę dziwny. Zobacz moją zaktualizowaną odpowiedź.
Gilles „SO- przestań być zły”
1
W odniesieniu do tematu „W trybie bash tablice zachowują się w inny i zaskakujący sposób”. zachowanie można wyjaśnić na stronach podręcznika bash (1), sekcja „Tablice”. Stwierdzono, że „Odwoływanie się do zmiennej tablicowej bez indeksu dolnego jest równoważne z odwoływaniem się do tablicy za pomocą indeksu dolnego 0.”. Zatem jeśli ani 0indeks, ani klucz nie są zdefiniowane tak, jak są prawdziwe a=(), ${a+1}poprawnie nic nie zwraca.
Tim Friske
1
@TimFriske Wiem, że implementacja bash jest zgodna z jej dokumentacją. Ale traktowanie pustej tablicy jak niezdefiniowanej zmiennej jest naprawdę dziwnym projektem.
Gilles „SO- przestań być zły”
Wolę wynik z: Jak sprawdzić, czy zmienna jest ustawiona w Bash? -> Właściwy sposób ... Uderza mnie akord tego, jak myślę, że to powinno działać. Odważę się powiedzieć, że Windows ma definedoperatora. Test mógłby to zrobić; to nie może być trudne ( um ....)
będzie
6

Podsumowując odpowiedź Gillesa, wymyśliłem następujące zasady:

  1. Posługiwać się [[ -v foobar ]] dla zmiennych w wersji Bash> = 4.2.
  2. Posługiwać się declare -p foobar &>/dev/null dla zmiennych tablicowych w wersji Bash <4.2.
  3. Użyj (( ${foo[0]+1} ))lub (( ${bar[foo]+1} ))dla indeksu dolnego odpowiednio tablicy indeksowanej ( -a) i keyed ( -A) declare. Opcje 1 i 2 nie działają tutaj.
Tim Friske
źródło
3

Używam tej samej techniki dla wszystkich zmiennych w bash, i to działa, np .:

[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

wyjścia:

foobar is unset

podczas

foobar=( "val" "val2" )
[ ${foobar} ] && echo "foobar is set" || echo "foobar is unset"

wyjścia:

foobar is set
Stein Inge Morisbak
źródło
Musiałem usunąć [@], jeśli tablica ma więcej niż jedną wartość.
Stein Inge Morisbak
1
Działa to świetnie, o ile chcesz sprawdzić, czy ma wartość, a nie czy została zdefiniowana. Tj. foobar=""Następnie to zgłosi foobar is unset. Nie czekaj, cofam to. Naprawdę sprawdza tylko, czy pierwszy element jest pusty, czy nie, więc wydaje się dobrym pomysłem, jeśli wiesz, że zmienna NIE jest tablicą i zależy ci tylko na pustce, a nie na definiowaniu.
Ron Burk,
działa tylko wtedy, gdy twoje skrypty działają z niezdefiniowanymi zmiennymi dozwolonymi (bez zestawu -u)
Florian Heigl