Was ist eine präzise Methode, um zu überprüfen, ob Umgebungsvariablen in einem Unix-Shell-Skript festgelegt sind?


500

Ich habe ein paar Unix-Shell-Skripte, in denen ich überprüfen muss, ob bestimmte Umgebungsvariablen festgelegt sind, bevor ich anfange, Dinge zu tun, also mache ich Folgendes:

if [ -z "$STATE" ]; then
    echo "Need to set STATE"
    exit 1
fi  

if [ -z "$DEST" ]; then
    echo "Need to set DEST"
    exit 1
fi

Das ist viel Tippen. Gibt es eine elegantere Redewendung, um zu überprüfen, ob eine Reihe von Umgebungsvariablen festgelegt ist?

BEARBEITEN: Ich sollte erwähnen, dass diese Variablen keinen aussagekräftigen Standardwert haben - das Skript sollte einen Fehler verursachen, wenn irgendwelche nicht gesetzt sind.


2
Viele der Antworten auf diese Frage scheinen etwas zu sein, das Sie bei Code Golf Stack Exchange sehen würden .
Daniel Schilling

Antworten:


587

Parametererweiterung

Die offensichtliche Antwort ist die Verwendung einer der speziellen Formen der Parametererweiterung:

: ${STATE?"Need to set STATE"}
: ${DEST:?"Need to set DEST non-empty"}

Oder besser (siehe Abschnitt "Position der doppelten Anführungszeichen" weiter unten):

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Für die erste Variante (mit just ?) muss STATE festgelegt werden, aber STATE = "" (eine leere Zeichenfolge) ist in Ordnung - nicht genau das, was Sie möchten, sondern die alternative und ältere Notation.

Bei der zweiten Variante (using :?) muss DEST gesetzt und nicht leer sein.

Wenn Sie keine Nachricht angeben, stellt die Shell eine Standardnachricht bereit.

Das ${var?}Konstrukt ist auf Version 7 UNIX und die Bourne Shell (1978 oder so ungefähr) portierbar. Das ${var:?}Konstrukt ist etwas jünger: Ich glaube, es war in System III UNIX um 1981, aber es war möglicherweise vorher in PWB UNIX. Es befindet sich daher in der Korn-Shell und in den POSIX-Shells, einschließlich speziell Bash.

Es wird normalerweise in der Manpage der Shell in einem Abschnitt namens Parametererweiterung dokumentiert . Zum Beispiel heißt es im bashHandbuch:

${parameter:?word}

Anzeigefehler bei Null oder Nicht gesetzt. Wenn der Parameter null oder nicht gesetzt ist, wird die Erweiterung des Wortes (oder eine entsprechende Meldung, wenn kein Wort vorhanden ist) in den Standardfehler geschrieben, und die Shell wird beendet, wenn sie nicht interaktiv ist. Andernfalls wird der Wert des Parameters ersetzt.

Der Doppelpunkt Befehl

Ich sollte wahrscheinlich hinzufügen, dass der Doppelpunkt einfach seine Argumente ausgewertet hat und dann erfolgreich ist. Dies ist die ursprüngliche Shell-Kommentarnotation (vor ' #' bis zum Zeilenende). Lange Zeit hatten Bourne-Shell-Skripte einen Doppelpunkt als erstes Zeichen. Die C-Shell las ein Skript und verwendete das erste Zeichen, um zu bestimmen, ob es sich um die C-Shell (ein #Hash) oder die Bourne-Shell (ein :Doppelpunkt) handelte. Dann mischte sich der Kernel ein und fügte Unterstützung für ' #!/path/to/program' hinzu , und die Bourne-Shell erhielt ' #' Kommentare, und die Doppelpunktkonvention blieb auf der Strecke. Wenn Sie jedoch auf ein Skript stoßen, das mit einem Doppelpunkt beginnt, wissen Sie jetzt warum.


Position von doppelten Anführungszeichen

Blong fragte in einem Kommentar :

Irgendwelche Gedanken zu dieser Diskussion? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

Der Kern der Diskussion ist:

… Wenn ich shellcheckes jedoch (mit Version 0.4.1) bekomme, erhalte ich folgende Meldung:

In script.sh line 13:
: ${FOO:?"The environment variable 'FOO' must be set and non-empty"}
  ^-- SC2086: Double quote to prevent globbing and word splitting.

Irgendwelche Ratschläge, was ich in diesem Fall tun soll?

Die kurze Antwort lautet "tun wie vorgeschlagen shellcheck":

: "${STATE?Need to set STATE}"
: "${DEST:?Need to set DEST non-empty}"

Um zu veranschaulichen, warum, studieren Sie Folgendes. Beachten Sie, dass der :Befehl seine Argumente nicht wiedergibt (die Shell wertet die Argumente jedoch aus). Wir wollen die Argumente sehen, daher wird der folgende Code printf "%s\n"anstelle von verwendet :.

$ mkdir junk
$ cd junk
$ > abc
$ > def
$ > ghi
$ 
$ x="*"
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
abc
def
ghi
$ unset x
$ printf "%s\n" ${x:?You must set x}    # Careless; not recommended
bash: x: You must set x
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
bash: x: You must set x
$ x="*"
$ printf "%s\n" "${x:?You must set x}"  # Careful: should be used
*
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
abc
def
ghi
$ x=
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ unset x
$ printf "%s\n" ${x:?"You must set x"}  # Not quite careful enough
bash: x: You must set x
$ 

Beachten Sie, wie der Wert in $xzuerst auf *und dann auf eine Liste von Dateinamen erweitert wird, wenn der Gesamtausdruck nicht in doppelte Anführungszeichen steht. Dies ist, was shellcheckempfohlen wird, sollte behoben werden. Ich habe nicht überprüft, dass es keine Einwände gegen das Formular hat, in dem der Ausdruck in doppelte Anführungszeichen eingeschlossen ist, aber es ist eine vernünftige Annahme, dass es in Ordnung wäre.


2
Das ist das, was ich brauche. Ich benutze seit 1987 verschiedene Versionen von Unix und habe diese Syntax noch nie gesehen - nur um zu zeigen ...
AndrewR

Wie funktioniert das und wo ist es dokumentiert? Ich frage mich, ob es geändert werden kann, um zu überprüfen, ob die Variable existiert und auf einen bestimmten Wert eingestellt ist.
Jhabbott

6
Es ist in der Shell-Handbuchseite oder im Bash-Handbuch dokumentiert, normalerweise unter der Überschrift 'Parametererweiterung'. Die Standardmethode zum Überprüfen, ob die Variable vorhanden ist und auf einen bestimmten Wert gesetzt ist (z. B. 'abc'), ist einfach : if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
Jonathan Leffler

Wie verwende ich diese Syntax mit einer Zeichenfolgenvariablen mit Leerzeichen? Ich erhalte die Fehlermeldung "This: Befehl nicht gefunden", wenn ich die zu überprüfende Variable auf "Dies ist ein Test" setze. Irgendwelche Ideen?
user2294382

1
Irgendwelche Gedanken zu dieser Diskussion? github.com/koalaman/shellcheck/issues/…
blong

138

Versuche dies:

[ -z "$STATE" ] && echo "Need to set STATE" && exit 1;

8
Das ist ziemlich ausführlich. Das Semikolon ist überflüssig.
Jonathan Leffler

3
Oder noch kürzer [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } siehe diesen Blog-Beitrag
zipizap

36
Was das ist, ist lesbar. Ich bin alles für diesen einen. Bash-Skripte brauchen alle Hilfe, die sie in dieser Abteilung bekommen können!
Iain Duncan

Die ursprüngliche Antwort ist in ihrer Syntax konsistenter.
schneller Zahn

4
Dies unterscheidet jedoch nicht zwischen einer nicht gesetzten Variablen und einer auf die leere Zeichenfolge gesetzten Variablen.
Chepner

57

Ihre Frage hängt von der Shell ab, die Sie verwenden.

Die Bourne-Muschel lässt nur sehr wenig im Weg, wonach Sie suchen.

ABER...

Es funktioniert fast überall.

Versuche einfach, dich von csh fernzuhalten. Es war gut für die Schnickschnack, die es hinzugefügt hat, verglichen mit der Bourne-Muschel, aber es knarrt jetzt wirklich. Wenn Sie mir nicht glauben, versuchen Sie einfach, STDERR in csh zu trennen! (-:

Hier gibt es zwei Möglichkeiten. Das obige Beispiel, nämlich mit:

${MyVariable:=SomeDefault}

Zum ersten Mal müssen Sie auf $ MyVariable verweisen. Dies nimmt die Umgebung. var MyVariable und weist der Variablen, falls sie derzeit nicht festgelegt ist, den Wert SomeDefault zur späteren Verwendung zu.

Sie haben auch die Möglichkeit:

${MyVariable:-SomeDefault}

Dies ersetzt nur SomeDefault für die Variable, in der Sie dieses Konstrukt verwenden. Der Variable wird der Wert SomeDefault nicht zugewiesen, und der Wert von MyVariable ist nach dem Auftreten dieser Anweisung immer noch null.


3
CSH: (foo> foo.out)> & foo.err
Mr.Ree

Die Bourne-Shell macht das, was benötigt wird.
Jonathan Leffler

51

Der einfachste Ansatz besteht sicherlich darin, den -uSchalter zum Shebang (der Zeile oben in Ihrem Skript) hinzuzufügen , vorausgesetzt, Sie verwenden bash:

#!/bin/sh -u

Dadurch wird das Skript beendet, wenn ungebundene Variablen darin lauern.


40
${MyVariable:=SomeDefault}

Wenn MyVariablegesetzt und nicht null, wird der Variablenwert zurückgesetzt (= nichts passiert).
Sonst MyVariableist eingestellt auf SomeDefault.

Der obige Vorgang wird ausgeführt ${MyVariable}. Wenn Sie also nur die Variable festlegen möchten, gehen Sie wie folgt vor :

MyVariable=${MyVariable:=SomeDefault}

Dadurch wird die Nachricht nicht generiert - und das Q gibt an, dass es keine Standardeinstellung gibt (obwohl dies bei der Eingabe Ihrer Antwort nicht angegeben wurde).
Jonathan Leffler

10
Richtige Versionen: (1): $ {MyVariable: = SomeDefault} oder (2): MyVariable = $ {MyVariable: -SomeDefault}
Jonathan Leffler

23

Meiner Meinung nach ist die einfachste und kompatibelste Prüfung für #! / Bin / sh :

if [ "$MYVAR" = "" ]
then
   echo "Does not exist"
else
   echo "Exists"
fi

Auch dies gilt für / bin / sh und ist auch auf alten Solaris-Systemen kompatibel.


Dies 'funktioniert', aber selbst alte Solaris-Systeme haben eine /bin/sh, die die ${var:?}Notation usw. unterstützt
Jonathan Leffler

Die bisher beste Antwort.
Ole

4
Das Setzen auf die leere Zeichenfolge unterscheidet sich vom Nicht-Setzen überhaupt nicht. Dieser Test würde nach beiden unset MYVARund "Existiert nicht" drucken MYVAR=.
Chepner

@chepner, ich habe Ihren Kommentar für das vermutlich wertvolle Heads-up positiv bewertet, aber dann habe ich ihn weiter getestet (mit Bash) und konnte eine Variable nicht auf eine leere Zeichenfolge setzen, ohne dass sie auch nicht gesetzt wurde. Wie würdest du das machen?
Gr.

@Sz Ich bin nicht sicher, was du meinst. "Nicht gesetzt" bedeutet, dass dem Namen überhaupt kein Wert zugewiesen wurde. Wenn foonicht gesetzt, wird "$foo"immer noch auf die leere Zeichenfolge erweitert.
Chepner

17

bash4.2 führte den -vOperator ein, der testet, ob ein Name auf einen beliebigen Wert gesetzt ist, sogar die leere Zeichenfolge.

$ unset a
$ b=
$ c=
$ [[ -v a ]] && echo "a is set"
$ [[ -v b ]] && echo "b is set"
b is set
$ [[ -v c ]] && echo "c is set"
c is set

16

Ich habe immer verwendet:

if [ "x$STATE" == "x" ]; then echo "Need to set State"; exit 1; fi

Ich fürchte, nicht viel prägnanter.

Unter CSH haben Sie $? STATE.


2
Dies prüft, ob STATEleer oder nicht gesetzt ist, nicht nur nicht gesetzt.
Chepner

7

Für zukünftige Leute wie mich wollte ich einen Schritt nach vorne gehen und den Variablennamen parametrisieren, damit ich eine Liste von Variablennamen mit variabler Größe durchlaufen kann:

#!/bin/bash
declare -a vars=(NAME GITLAB_URL GITLAB_TOKEN)

for var_name in "${vars[@]}"
do
  if [ -z "$(eval "echo \$$var_name")" ]; then
    echo "Missing environment variable $var_name"
    exit 1
  fi
done

3

Wir können eine nette Behauptung schreiben, um eine Reihe von Variablen gleichzeitig zu überprüfen:

#
# assert if variables are set (to a non-empty string)
# if any variable is not set, exit 1 (when -f option is set) or return 1 otherwise
#
# Usage: assert_var_not_null [-f] variable ...
#
function assert_var_not_null() {
  local fatal var num_null=0
  [[ "$1" = "-f" ]] && { shift; fatal=1; }
  for var in "$@"; do
    [[ -z "${!var}" ]] &&
      printf '%s\n' "Variable '$var' not set" >&2 &&
      ((num_null++))
  done

  if ((num_null > 0)); then
    [[ "$fatal" ]] && exit 1
    return 1
  fi
  return 0
}

Beispielaufruf:

one=1 two=2
assert_var_not_null one two
echo test 1: return_code=$?
assert_var_not_null one two three
echo test 2: return_code=$?
assert_var_not_null -f one two three
echo test 3: return_code=$? # this code shouldn't execute

Ausgabe:

test 1: return_code=0
Variable 'three' not set
test 2: return_code=1
Variable 'three' not set

Weitere solche Behauptungen finden Sie hier: https://github.com/codeforester/base/blob/master/lib/assertions.sh



1

Keine der oben genannten Lösungen hat für meine Zwecke funktioniert, auch weil ich die Umgebung auf eine offene Liste von Variablen überprüft habe, die festgelegt werden müssen, bevor ein langer Prozess gestartet wird. Am Ende hatte ich Folgendes:

mapfile -t arr < variables.txt

EXITCODE=0

for i in "${arr[@]}"
do
   ISSET=$(env | grep ^${i}= | wc -l)
   if [ "${ISSET}" = "0" ];
   then
      EXITCODE=-1
      echo "ENV variable $i is required."
   fi
done

exit ${EXITCODE}

-1

Anstatt externe Shell-Skripte zu verwenden, lade ich eher Funktionen in meine Login-Shell. Ich verwende so etwas als Hilfsfunktion, um nach Umgebungsvariablen zu suchen, anstatt nach einer festgelegten Variablen:

is_this_an_env_variable ()
    local var="$1"
    if env |grep -q "^$var"; then
       return 0
    else
       return 1
    fi
 }

1
Das ist falsch. is_this_an_env_variable Pwird Erfolg zurückgeben, weil PATHexistiert.
Eric

Es existiert, weil es eine Umgebungsvariable ist. Ob es sich um eine nicht leere Zeichenfolge handelt, ist eine andere Frage.
Nafdef

-7

Die $?Syntax ist ziemlich ordentlich:

if [ $?BLAH == 1 ]; then 
    echo "Exists"; 
else 
    echo "Does not exist"; 
fi

Das funktioniert, aber weiß hier jemand, wo ich Dokumente zu dieser Syntax finden kann? $? ist normalerweise der vorherige Rückgabewert.
Danny Staple

Mein schlechtes - das scheint nicht zu funktionieren. Probieren Sie es in einem einfachen Skript mit und ohne dieses Set aus.
Danny Staple

11
In Bourne-, Korn-, Bash- und POSIX-Shells $?ist der Exit-Status des vorherigen Befehls. $?BLAHist die Zeichenfolge, die aus dem Exit-Status des vorherigen Befehls besteht, der mit 'BLAH' verknüpft ist. Dies testet die Variable $BLAHüberhaupt nicht. (Es gibt ein Gerücht, dass es mehr oder weniger tun könnte, was erforderlich ist csh, aber Muscheln werden am besten an der Küste gelassen.)
Jonathan Leffler

Dies ist für Bash eine völlig falsche Antwort.
Codeforester
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.