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.