Wie kann man testen, ob eine Variable in Bash vor Version 4.2 mit der Shell-Option nounset definiert wurde?

16

Gibt es für Bash-Versionen vor "GNU bash, Version 4.2" gleichwertige Alternativen für die -vOption des testBefehls? Beispielsweise:

shopt -os nounset
test -v foobar && echo foo || echo bar
# Output: bar
foobar=
test -v foobar && echo foo || echo bar
# Output: foo
Tim Friske
quelle
-vist keine Option für test, sondern ein Operator für bedingte Ausdrücke.
Tim
@ Tim Es drei Dinge ist, neben ein Zeichen sein, eine Zeichenfolge und einen Teil einer Linie: Ein optionauf einen Befehl test -v, eine operatorauf ein conditional expressionund unary test primaryfür [ ]. Mischen Sie nicht die englische Sprache mit Shell-Definitionen.

Antworten:

36

Portabel auf alle POSIX-Shells:

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

Machen Sie das, ${foobar:+1}wenn Sie gleich behandeln möchten, foobarob es leer oder nicht definiert ist. Sie können auch verwenden ${foobar-}, um eine leere Zeichenfolge abzurufen, wenn foobarundefiniert und der Wert von " foobarelse" (oder einen anderen Standardwert nach " -).

In KSH, wenn foobarerklärt wird , aber nicht definiert, wie in typeset -a foobar, dann ${foobar+1}auf die leere Zeichenfolge erweitert.

Zsh hat keine deklarierten, aber nicht gesetzten Variablen: Erstellt typeset -a foobarein leeres Array.

In bash verhalten sich Arrays anders und überraschend. ${a+1}wird nur erweitert, 1wenn aes sich um ein nicht leeres Array handelt, z

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

Dasselbe Prinzip gilt für assoziative Arrays: Array-Variablen werden wie definiert behandelt, wenn sie eine nicht leere Menge von Indizes enthalten.

Eine andere, bash-spezifische Methode zum Testen, ob eine Variable eines beliebigen Typs definiert wurde, besteht darin, zu überprüfen, ob sie in aufgeführt ist . Dies meldet leere Arrays als definiert, im Gegensatz zu deklarierten aber nicht zugewiesenen Variablen ( ) jedoch als undefiniert.${!PREFIX*}${foobar+1}unset foobar; typeset -a foobar

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

Dies entspricht dem Testen des Rückgabewerts von typeset -p foobaroderdeclare -p foobar , mit der Ausnahme, dass typeset -p foobarbei deklarierten, aber nicht zugewiesenen Variablen ein Fehler auftritt.

In bash wird wie in ksh set -o nounset; typeset -a foobar; echo $foobarbeim Versuch, die undefinierte Variable zu erweitern , ein Fehler ausgelöst foobar. Anders als in ksh löst set -o nounset; foobar=(); echo $foobar(oder echo "${foobar[@]}") auch einen Fehler aus.

Beachten Sie, dass in allen hier beschriebenen Situationen ${foobar+1}nur dann $foobareine Erweiterung auf die leere Zeichenfolge erfolgt, wenn dies zu einem Fehler unter führen würde set -o nounset.

Gilles 'SO - hör auf böse zu sein'
quelle
1
Was ist mit Arrays? In der Bash-Version "GNU bash, Version 4.1.10 (4) -release (i686-pc-cygwin)" echo "${foobar:+1}"wird nicht gedruckt, 1wenn sie declare -a foobarzuvor ausgegeben wurde, und foobarist daher ein indiziertes Array. declare -p foobarrichtig berichtet declare -a foobar='()'. Funktioniert "${foobar:+1}"nur für Nicht-Array-Variablen?
Tim Friske
@TimFriske ${foobar+1}(ohne das :, ich habe zwei Beispiele in meiner ursprünglichen Antwort invertiert) ist für Arrays in bash korrekt, wenn Ihre Definition von "defined" "would $foobarwork under set -o nounset" lautet . Wenn Ihre Definition anders ist, ist bash ein bisschen komisch. Siehe meine aktualisierte Antwort.
Gilles 'SO - hör auf böse zu sein'
1
Zum Thema "In bash verhalten sich Arrays anders und überraschend." Das Verhalten kann aus den bash (1) -Manpages im Abschnitt "Arrays" erklärt werden. Darin heißt es: "Das Verweisen auf eine Arrayvariable ohne Index entspricht dem Verweisen auf das Array mit dem Index 0". Wenn also weder ein 0Index noch ein Schlüssel als wahr definiert sind a=(), ${a+1}wird nichts korrekt zurückgegeben.
Tim Friske
1
@ TimFriske Ich weiß, dass die Bash-Implementierung der Dokumentation entspricht. Aber ein leeres Array wie eine undefinierte Variable zu behandeln, ist wirklich seltsam.
Gilles 'SO - hör auf böse zu sein'
Ich bevorzuge das Ergebnis von: Wie überprüfe ich , ob eine Variable in Bash gesetzt ist? -> Der richtige Weg ... Ich finde es klasse, wie das funktionieren soll. Ich wage zu sagen, Windows hat einen definedOperator. Test könnte das tun; es kann nicht schwierig sein ( ähm ...)
wird am
5

Um mit Gilles 'Antwort zusammenzufassen, habe ich folgende Regeln aufgestellt:

  1. Verwenden Sie [[ -v foobar ]]für Variablen in der Bash-Version> = 4.2.
  2. Verwenden Sie declare -p foobar &>/dev/nullfür Array-Variablen in Bash-Version <4.2.
  3. Verwenden Sie (( ${foo[0]+1} ))bzw. (( ${bar[foo]+1} ))für Indizes von indizierten ( -a) und verschlüsselten ( -A) Arrays ( declare). Option 1 und 2 funktionieren hier nicht.
Tim Friske
quelle
3

Ich verwende die gleiche Technik für alle Variablen in Bash, und es funktioniert, zB:

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

Ausgänge:

foobar is unset

während

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

Ausgänge:

foobar is set
Stein Inge Morisbak
quelle
Musste [@] entfernen, wenn das Array mehr als einen Wert hat.
Stein Inge Morisbak
1
Dies funktioniert hervorragend, solange Sie testen möchten, ob ein Wert vorhanden ist, und nicht, ob er definiert wurde. Dh, foobar=""werde das dann melden foobar is unset. Nein, ich nehme das zurück. Tatsächlich wird nur geprüft, ob das erste Element leer ist oder nicht. Daher ist es nur eine gute Idee, wenn Sie wissen, dass die Variable KEIN Array ist und Sie sich nur um die Leere und nicht um die Definiertheit kümmern.
Ron Burk,
Funktioniert nur, wenn Ihre Skripte mit undefinierten Variablen ausgeführt werden dürfen (kein Set -u)
Florian Heigl