Jaki jest zwięzły sposób sprawdzenia, czy zmienne środowiskowe są ustawione w skrypcie powłoki Uniksa?


500

Mam kilka skryptów powłoki Unixa, w których muszę sprawdzić, czy pewne zmienne środowiskowe są ustawione, zanim zacznę coś robić, więc robię coś takiego:

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

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

co jest dużo pisania. Czy istnieje bardziej elegancki idiom sprawdzający, czy zestaw zmiennych środowiskowych jest ustawiony?

EDYCJA: Powinienem wspomnieć, że te zmienne nie mają żadnej znaczącej wartości domyślnej - skrypt powinien błędnie skasować, jeśli są ustawione


2
Wiele odpowiedzi na to pytanie wydaje się czymś, co można zobaczyć na Code Golf Stack Exchange .
— Daniel Schilling

Odpowiedzi:


587

Rozszerzenie parametru

Oczywistą odpowiedzią jest użycie jednej ze specjalnych form rozszerzania parametrów:

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

Lub lepiej (patrz sekcja „Pozycja podwójnych cudzysłowów” poniżej):

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

Pierwszy wariant (używając just ?) wymaga ustawienia STATE, ale STATE = "" (pusty ciąg znaków) jest OK - nie dokładnie to, czego chcesz, ale alternatywna i starsza notacja.

Drugi wariant (użycie :?) wymaga ustawienia DEST i niepustego.

Jeśli nie podasz żadnego komunikatu, powłoka wyświetli komunikat domyślny.

${var?}Konstrukt jest przenośny powrotem do Version 7 UNIX i Bourne Shell (1978) lub w jej pobliżu. ${var:?}Konstrukt jest nieco nowsza: Myślę, że to było w systemie III UNIX około 1981, ale może to być w PWB UNIX wcześniej. Znajduje się zatem w powłoce Korna i powłokach POSIX, w tym szczególnie w Bash.

Zwykle jest to udokumentowane na stronie podręcznika powłoki w sekcji o nazwie Rozszerzenie parametru . Na przykład bashinstrukcja mówi:

${parameter:?word}

Błąd wyświetlania, jeśli Null lub Unset. Jeśli parametr ma wartość NULL lub jest nieustawiony, rozwinięcie słowa (lub komunikat o tym, jeśli słowo nie jest obecne) jest zapisywany do standardowego błędu i powłoka, jeśli nie jest interaktywna, kończy działanie. W przeciwnym razie wartość parametru zostanie podstawiona.

Komenda dwukropka

Powinienem chyba dodać, że polecenie dwukropka po prostu ocenia swoje argumenty, a następnie się udaje. Jest to oryginalna notacja komentarza powłoki (przed „ #” do końca wiersza). Przez długi czas pierwsze skrypty skryptów Bourne'a miały dwukropek. Powłoka C czytałaby skrypt i używałaby pierwszego znaku, aby ustalić, czy chodzi o Powłokę C ( #skrót), czy powłokę Bourne'a ( :dwukropek). Następnie jądro wzięło udział w akcji i dodało wsparcie dla komentarzy, a #!/path/to/programpowłoka Bourne'a dostała #komentarze, a konwencja okrężnicy poszła na marne. Ale jeśli natrafisz na skrypt rozpoczynający się dwukropkiem, teraz będziesz wiedział, dlaczego.


Pozycja podwójnych cytatów

blong zapytał w komentarzu :

Jakieś przemyślenia na temat tej dyskusji? https://github.com/koalaman/shellcheck/issues/380#issuecomment-145872749

Istotą dyskusji jest:

… Jednak kiedy shellcheckto zrobię (w wersji 0.4.1), otrzymuję ten komunikat:

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.

Wszelkie porady dotyczące tego, co powinienem zrobić w tym przypadku?

Krótka odpowiedź brzmi „rób jak shellchecksugeruje”:

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

Aby zilustrować przyczynę, przestudiuj poniższe. Zauważ, że :polecenie nie powtarza swoich argumentów (ale powłoka ocenia argumenty). Chcemy zobaczyć argumenty, więc poniższy kod używa printf "%s\n"zamiast :.

$ 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
$ 

Zwróć uwagę, w jaki sposób wartość in $xjest rozwijana do najpierw, *a następnie listy nazw plików, gdy ogólne wyrażenie nie jest w cudzysłowach. To, co shellcheckzaleca, powinno zostać naprawione. Nie sprawdziłem, czy nie sprzeciwia się formie, w której wyrażenie jest ujęte w podwójne cudzysłowy, ale uzasadnione jest założenie, że byłoby OK.


2
Właśnie tego potrzebuję. Używam różnych wersji Uniksa od 1987 roku i nigdy nie widziałem tej składni - po prostu pokazuje ...
— AndrewR,

Jak to działa i gdzie jest udokumentowane? Zastanawiam się, czy można go zmodyfikować, aby sprawdzić, czy zmienna istnieje i czy jest ustawiona na określoną wartość.
— jhabbott,

6
Jest to udokumentowane na stronie podręcznika powłoki lub podręczniku Bash, zwykle pod nagłówkiem „Rozszerzanie parametrów”. Standardowym sposobem sprawdzenia, czy zmienna istnieje i jest ustawiony na wartość konkretnego (powiedzmy „abc”) jest po prostu: if [ "$variable" = "abc" ]; then : OK; else : variable is not set correctly; fi.
— Jonathan Leffler,

Jak korzystać z tej składni ze zmienną łańcuchową ze spacjami. Gdy ustawiam zmienną, chcę sprawdzić „To jest test”, pojawia się błąd „To: polecenie nie znaleziono”. Jakieś pomysły?
— user2294382,

1
Jakieś przemyślenia na temat tej dyskusji? github.com/koalaman/shellcheck/issues/…
— blong

138

Spróbuj tego:

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

8
To raczej gadatliwe. Średnik jest zbyteczny.
— Jonathan Leffler

3
Lub jeszcze krócej [ -z "$STATE" ] && { echo "Need to set STATE"; exit 1; } zobacz ten post na blogu
— zipizap

36
Co to jest, jest czytelne. Jestem za tym wszystkim. Skrypty Bash potrzebują wszelkiej pomocy, jaką mogą uzyskać w tym dziale!
— Iain Duncan

oryginalna odpowiedź jest bardziej spójna pod względem składni.
— szybki ząb

4
Nie rozróżnia to jednak zmiennej nieustawionej od zmiennej ustawionej na pusty ciąg.
— chepner

57

Twoje pytanie zależy od używanej powłoki.

Skorupa Bourne'a pozostawia bardzo mało na drodze do tego, czego szukasz.

ALE...

Działa, prawie wszędzie.

Po prostu staraj się trzymać z dala od csh. To było dobre dla dzwonków i gwizdów, które dodał, w porównaniu do skorupy Bourne'a, ale teraz naprawdę trzeszczy. Jeśli mi nie wierzysz, po prostu spróbuj rozdzielić STDERR w csh! (-:

Istnieją tutaj dwie możliwości. Powyższy przykład, a mianowicie użycie:

${MyVariable:=SomeDefault}

po raz pierwszy musisz zapoznać się z $ MyVariable. To zajmuje środowisko. var MyVariable i, jeśli nie jest aktualnie ustawiony, przypisuje wartość SomeDefault do zmiennej do późniejszego wykorzystania.

Masz również możliwość:

${MyVariable:-SomeDefault}

który po prostu zastępuje SomeDefault zmienną, w której używasz tej konstrukcji. Nie przypisuje wartości SomeDefault do zmiennej, a wartość MyVariable nadal będzie pusta po napotkaniu tej instrukcji.


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

Powłoka Bourne'a robi to, co jest wymagane.
— Jonathan Leffler

51

Z pewnością najprostszym podejściem jest dodanie -uprzełącznika do shebang (linii u góry skryptu), zakładając, że używasz bash:

#!/bin/sh -u

Spowoduje to zamknięcie skryptu, jeśli czają się w nim niezwiązane zmienne.


40
${MyVariable:=SomeDefault}

Jeśli MyVariablejest ustawiony, a nie null, zresetuje wartość zmiennej (= nic się nie dzieje).
W przeciwnym razie MyVariableustawiona jest wartość SomeDefault.

Powyższe spróbuje wykonać ${MyVariable}, więc jeśli chcesz ustawić zmienną, wykonaj:

MyVariable=${MyVariable:=SomeDefault}

To nie generuje wiadomości - a Q mówi, że nie ma wartości domyślnej (chociaż nie powiedział tak podczas wpisywania odpowiedzi).
— Jonathan Leffler

10
Poprawne wersje: (1): $ {MyVariable: = SomeDefault} lub (2): MyVariable = $ {MyVariable: -SomeDefault}
— Jonathan Leffler

23

Moim zdaniem najprostszym i najbardziej zgodnym sprawdzaniem #! / Bin / sh jest:

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

Ponownie, dotyczy to / bin / sh i jest kompatybilny również ze starymi systemami Solaris.


To „działa”, ale nawet stare systemy Solaris mają /bin/shtaką ${var:?}notację itp.
— Jonathan Leffler

Najlepsza jak dotąd odpowiedź.
— Ole

4
Ustawienie pustego łańcucha różni się od braku ustawienia. Ten test wyświetli „Nie istnieje” po obu unset MYVARi MYVAR=.
— chepner

@chepner, zagłosowałem za twoim komentarzem za przypuszczalnie cenny heads-up, ale potem kontynuowałem testowanie go (z bash) i nie mogłem tak naprawdę ustawić var ​​na pusty ciąg bez tego, że byłby również rozbrojony. Jak byś to zrobił?
— Sz.

@ Sz Nie jestem pewien, co masz na myśli. „Unset” oznacza, że ​​nazwa nie ma żadnej przypisanej wartości. Jeśli nie foojest ustawiony, "$foo"nadal rozwija się do pustego ciągu.
— chepner

17

bash4.2 wprowadził -voperator, który sprawdza, czy nazwa ma jakąkolwiek wartość, nawet pusty ciąg znaków.

$ 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

Zawsze używałem:

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

Obawiam się, że nie jest to bardziej zwięzłe.

W ramach CSH masz $? STATE.


2
Sprawdza, czy STATEjest pusty czy rozbrojony, a nie tylko rozbrojony.
— chepner

7

Dla przyszłych ludzi takich jak ja chciałem zrobić krok do przodu i sparametryzować nazwę zmiennej, aby móc przeglądać listę nazw zmiennych o zmiennej wielkości:

#!/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

Możemy napisać niezłe twierdzenie, aby sprawdzić kilka zmiennych jednocześnie:

#
# 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
}

Przykładowe wywołanie:

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

Wynik:

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

Więcej takich twierdzeń tutaj: https://github.com/codeforester/base/blob/master/lib/assertions.sh



1

Żadne z powyższych rozwiązań nie zadziałało dla moich celów, częściowo dlatego, że sprawdzam środowisko pod kątem otwartej listy zmiennych, które należy ustawić przed rozpoczęciem długiego procesu. Skończyło się na tym:

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

Zamiast używać zewnętrznych skryptów powłoki, zwykle ładuję funkcje w mojej powłoce logowania. Używam czegoś takiego jako funkcji pomocniczej do sprawdzania zmiennych środowiskowych zamiast jakiejkolwiek zmiennej ustawionej:

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

1
To jest źle. is_this_an_env_variable Pzwróci sukces, ponieważ PATHistnieje.
— Eric

Istnieje, ponieważ jest zmienną środowiskową. To, czy zostało ustawione, eqaul nieparzysty ciąg to inna sprawa.
— nafdef

-7

$?Składnia jest bardzo czysty:

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

To działa, ale czy ktoś tu wie, gdzie mogę znaleźć dokumenty dotyczące tej składni? $? jest zwykle poprzednią wartością zwracaną.
— Danny Staple

Mój zły - to nie wydaje się działać. Wypróbuj w prostym skrypcie, z tym zestawem i bez niego.
— Danny Staple

11
W powłokach Bourne, Korn, Bash i POSIX $?jest to status wyjścia poprzedniego polecenia; $?BLAHto ciąg znaków składający się ze statusu wyjścia poprzedniego polecenia połączonego z „BLAH”. To wcale nie testuje zmiennej $BLAH. (Krąży plotka, że ​​może to zrobić mniej więcej to, co jest wymagane csh, ale muszle morskie najlepiej zostawić na brzegu morza.)
— Jonathan Leffler

Jest to całkowicie błędna odpowiedź, jeśli chodzi o Bash.
— codeforester
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.