Jak sprawdzić, czy ciąg nie jest zdefiniowany w skrypcie powłoki Bash

151

Jeśli chcę sprawdzić ciąg pusty, zrobiłbym to

[ -z $mystr ]

ale co jeśli chcę sprawdzić, czy zmienna została w ogóle zdefiniowana? A może nie ma rozróżnienia w skryptach Bash?

Setjmp
źródło
27
miej zwyczaj używania [-z "$ mystr"] w przeciwieństwie do [-z $ mystr]
Charles Duffy,
jak zrobić odwrotną rzecz? Mam na myśli, kiedy ciąg nie jest zerowy
Otwórz drogę
1
@flow: A co z[ -n "${VAR+x}"] && echo not null
Lekensteyn
1
@CharlesDuffy Przeczytałem to już wcześniej w wielu zasobach internetowych. Dlaczego jest to preferowane?
ffledgling
6
@Ayos, ponieważ jeśli nie masz cudzysłowów, zawartość zmiennej jest dzielona na ciągi znaków i umieszczana globalnie; jeśli jest to pusty ciąg, staje się [ -z ]zamiast [ -z "" ]; jeśli ma spacje, staje się [ -z "my" "test" ]zamiast [ -z my test ]; a jeśli tak [ -z * ], to *jest zastępowane nazwami plików w twoim katalogu.
Charles Duffy,

Odpowiedzi:

138

Myślę, że odpowiedź jesteś po zakłada się (jeśli nie zaznaczono) przez Vinko „s odpowiedź , choć nie jest to napisane wprost. Aby rozróżnić, czy WARIANCJA jest ustawiona, ale pusta, czy nie, możesz użyć:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "$VAR" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Prawdopodobnie możesz połączyć dwa testy w drugiej linii w jeden z:

if [ -z "$VAR" -a "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Jeśli jednak przeczytasz dokumentację Autoconf, zauważysz, że nie zalecają łączenia terminów z „ -a” i zalecają używanie oddzielnych prostych testów połączonych z &&. Nie spotkałem systemu, w którym jest problem; to nie znaczy, że kiedyś nie istniały (ale prawdopodobnie są obecnie niezwykle rzadkie, nawet jeśli nie były tak rzadkie w odległej przeszłości).

Szczegóły tych i innych powiązanych rozszerzeń parametrów powłoki , polecenia testlub[ oraz wyrażeń warunkowych można znaleźć w podręczniku Bash.


Niedawno zostałem zapytany przez e-mail o tę odpowiedź z pytaniem:

Używasz dwóch testów i dobrze rozumiem drugi, ale nie pierwszy. Dokładniej, nie rozumiem potrzeby rozwijania zmiennych

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi

Czy to nie przyniesie tego samego?

if [ -z "${VAR}" ]; then echo VAR is not set at all; fi

Uczciwe pytanie - odpowiedź brzmi: „Nie, Twoja prostsza alternatywa nie daje tego samego”.

Załóżmy, że napiszę to przed twoim testem:

VAR=

Twój test powie „WARIANCJA nie jest w ogóle ustawiona”, ale moja powie (przez domniemanie, ponieważ nic nie powtarza) „WARIANCJA jest ustawiona, ale jej wartość może być pusta”. Wypróbuj ten skrypt:

(
unset VAR
if [ -z "${VAR+xxx}" ]; then echo JL:1 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:1 VAR is not set at all; fi
VAR=
if [ -z "${VAR+xxx}" ]; then echo JL:2 VAR is not set at all; fi
if [ -z "${VAR}" ];     then echo MP:2 VAR is not set at all; fi
)

Wynik to:

JL:1 VAR is not set at all
MP:1 VAR is not set at all
MP:2 VAR is not set at all

W drugiej parze testów zmienna jest ustawiona, ale ma wartość pustą. To jest różnica, którą wprowadzają notacje ${VAR=value}i ${VAR:=value}. To samo dotyczy ${VAR-value}i ${VAR:-value}, i ${VAR+value}i ${VAR:+value}, i tak dalej.


Jak wskazuje Gili w swojej odpowiedzi , jeśli uruchomisz bashset -o nounsetopcję, podstawowa odpowiedź powyżej zawodzi unbound variable. Można to łatwo naprawić:

if [ -z "${VAR+xxx}" ]; then echo VAR is not set at all; fi
if [ -z "${VAR-}" ] && [ "${VAR+xxx}" = "xxx" ]; then echo VAR is set but empty; fi

Lub możesz anulować set -o nounsetopcję za pomocą set +u( set -ubędącego odpowiednikiem set -o nounset).

Jonathan Leffler
źródło
7
Jedynym problemem, jaki mam z tą odpowiedzią, jest to, że spełnia ona swoje zadanie w sposób raczej pośredni i niejasny.
Szwajcaria
3
Dla tych, którzy chcą znaleźć opis tego, co oznacza powyższe na stronie podręcznika bash, poszukaj sekcji „Rozwijanie parametrów”, a następnie tego tekstu: „Gdy nie wykonujesz interpretacji podciągów, używając formularzy udokumentowanych poniżej, bash testuje parametr, który jest nieustawiony lub pusty [„null” oznacza pusty ciąg]. Pominięcie dwukropka powoduje test tylko dla nieustawionego parametru (...) $ {parametr: + słowo}: Użyj wartości alternatywnej. Jeśli parametr jest pusty lub nieustawiony, nic nie jest podstawiane, w przeciwnym razie podstawiana jest interpretacja słowa. "
David Tonhofer
2
@Swiss Nie jest to ani niejasne, ani pośrednie, ale idiomatyczne. Być może dla nieznanego programisty ${+}i ${-}jest to niejasne, ale znajomość tych konstrukcji jest niezbędna, jeśli chce się być kompetentnym użytkownikiem powłoki.
William Pursell,
Zobacz stackoverflow.com/a/20003892/14731, jeśli chcesz to zaimplementować z set -o nounsetwłączoną opcją.
Gili
Przeprowadziłem wiele testów na tym; ponieważ wyniki w moich skryptach były niespójne. Proponuję zapoznać ludowej do „ [ -v VAR ]” podejście z bash -s v4.2 i zaświatach .
będzie
38
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO=""
~> if [ -z $FOO ]; then echo "EMPTY"; fi
EMPTY
~> FOO="a"
~> if [ -z $FOO ]; then echo "EMPTY"; fi
~> 

-z działa również dla niezdefiniowanych zmiennych. Aby rozróżnić niezdefiniowane i zdefiniowane, użyj rzeczy wymienionych tutaj lub, z jaśniejszymi wyjaśnieniami, tutaj .

Najczystszym sposobem jest użycie ekspansji, jak w tych przykładach. Aby uzyskać wszystkie opcje, zapoznaj się z sekcją Rozszerzanie parametrów w instrukcji.

Alternatywne słowo:

~$ unset FOO
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
~$ FOO=""
~$ if test ${FOO+defined}; then echo "DEFINED"; fi
DEFINED

Domyślna wartość:

~$ FOO=""
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
~$ unset FOO
~$ if test "${FOO-default value}" ; then echo "UNDEFINED"; fi
UNDEFINED

Oczywiście możesz użyć jednego z nich w inny sposób, umieszczając żądaną wartość zamiast „wartości domyślnej” i, jeśli to konieczne, bezpośrednio używając rozszerzenia.

Vinko Vrsalovic
źródło
1
Ale chcę rozróżnić, czy ciąg jest „”, czy też nie został nigdy zdefiniowany. Czy to jest możliwe?
Setjmp
1
Ta odpowiedź jest powiedzieć jak odróżnić tych dwóch przypadkach; podążaj za odnośnikiem bash faq, który zapewnia więcej dyskusji.
Charles Duffy,
1
Dodałem, że po jego komentarzu Charles
Vinko Vrsalovic
2
Poszukaj "Rozszerzenia parametrów" na stronie podręcznika bash dla wszystkich tych "sztuczek". Np. $ {Foo: -default}, aby użyć wartości domyślnej, $ {foo: = default}, aby przypisać wartość domyślną, $ {foo:? Komunikat o błędzie}, aby wyświetlić komunikat o błędzie, jeśli foo nie jest ustawione itp.
Jouni K , Seppänen
18

Zaawansowany przewodnik po skryptach bash, 10.2. Zastępowanie parametrów:

  • $ {var + blahblah}: jeśli zdefiniowano var, to wyrażenie „blahblah” jest podstawiane, w przeciwnym razie podstawiane jest null
  • $ {var-blahblah}: jeśli zdefiniowano var, sam jest podstawiany, w przeciwnym razie podstawiane jest słowo „blahblah”
  • $ {var? blahblah}: jeśli zdefiniowano zmienną, jest ona zastępowana, w przeciwnym razie funkcja istnieje z „blahblah” jako komunikatem o błędzie.


aby oprzeć logikę programu na tym, czy zmienna $ mystr jest zdefiniowana, czy nie, możesz wykonać następujące czynności:

isdefined=0
${mystr+ export isdefined=1}

teraz, jeśli isdefined = 0 to zmienna była niezdefiniowana, jeśli isdefined = 1 to zmienna została zdefiniowana

Ten sposób sprawdzania zmiennych jest lepszy niż powyższa odpowiedź, ponieważ jest bardziej elegancki, czytelny i jeśli Twoja powłoka bash została skonfigurowana tak, aby zawierała błąd przy użyciu niezdefiniowanych zmiennych (set -u), skrypt zakończy działanie przedwcześnie.


Inne przydatne rzeczy:

aby mieć domyślną wartość 7 przypisaną do $ mystr, jeśli była niezdefiniowana, i pozostawić ją nienaruszoną w przeciwnym razie:

mystr=${mystr- 7}

aby wydrukować komunikat o błędzie i wyjść z funkcji, jeśli zmienna jest niezdefiniowana:

: ${mystr? not defined}

Uważaj tutaj, że użyłem ':', aby zawartość $ mystr nie była wykonywana jako polecenie w przypadku, gdy jest zdefiniowana.


źródło
Nie wiedziałem o? składnia dla zmiennych BASH. To niezły haczyk.
Szwajcaria
Działa to również w przypadku pośrednich odwołań do zmiennych. Ten przekazuje: var= ; varname=var ; : ${!varname?not defined}a to kończy: varname=var ; : ${!varname?not defined}. Ale jest to dobry nawyk, set -uktóry robi to samo znacznie łatwiej.
ceving
11

Podsumowanie testów.

[ -n "$var" ] && echo "var is set and not empty"
[ -z "$var" ] && echo "var is unset or empty"
[ "${var+x}" = "x" ] && echo "var is set"  # may or may not be empty
[ -n "${var+x}" ] && echo "var is set"  # may or may not be empty
[ -z "${var+x}" ] && echo "var is unset"
[ -z "${var-x}" ] && echo "var is set and empty"
k107
źródło
Dobra praktyka w bash. Czasami ścieżki plików mają spacje lub w celu ochrony przed wstrzyknięciem poleceń
k107
1
Myślę, że ${var+x}nie jest to konieczne, chyba że nazwy zmiennych mogą zawierać spacje.
Anne van Rossum
Nie tylko dobra praktyka; to wymagane ze [aby uzyskać poprawne wyniki. Jeśli varnie jest ustawiony ani pusty, [ -n $var ]zmniejsza się [ -n ], która ma stan wyjścia 0, podczas gdy [ -n "$var" ]posiada przewidywaną (poprawne) stan wyjściowy o 1.
chepner
5

https://stackoverflow.com/a/9824943/14731 zawiera lepszą odpowiedź (taką, która jest bardziej czytelna i działa z set -o nounsetwłączoną opcją). Działa mniej więcej tak:

if [ -n "${VAR-}" ]; then
    echo "VAR is set and is not empty"
elif [ "${VAR+DEFINED_BUT_EMPTY}" = "DEFINED_BUT_EMPTY" ]; then
    echo "VAR is set, but empty"
else
    echo "VAR is not set"
fi
Gili
źródło
4

Jawny sposób sprawdzenia definiowanej zmiennej to:

[ -v mystr ]
Felix Leipold
źródło
1
Nie mogę znaleźć tego w żadnej ze stron podręcznika systemowego (dla basha lub testu), ale to działa dla mnie. Masz pojęcie, jakie wersje to zostało dodane?
Ash Berlin-Taylor
1
Jest udokumentowane w wyrażeniach warunkowych , do których odwołuje się testoperator na końcu sekcji Wbudowane powłoki Bourne'a . Nie wiem, kiedy został dodany, ale nie ma go w wersji Apple bash(opartej na bash3.2.51), więc prawdopodobnie jest to funkcja 4.x.
Jonathan Leffler
1
To mi nie pasuje GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu). Błąd jest -bash: [: -v: unary operator expected. Zgodnie z komentarzem tutaj , do działania wymaga minimum bash 4.2.
Acumenus
Dziękujemy za sprawdzenie, kiedy stało się dostępne. Używałem go wcześniej w różnych systemach, ale nagle przestał działać. Przyszedłem tu z ciekawości i tak, oczywiście bash w tym systemie to bash 3.x.
clacke
1

inna opcja: rozwinięcie "list array indices" :

$ unset foo
$ foo=
$ echo ${!foo[*]}
0
$ foo=bar
$ echo ${!foo[*]}
0
$ foo=(bar baz)
$ echo ${!foo[*]}
0 1

jedyny przypadek, w którym rozwija się to do pustego ciągu, to kiedy foojest nieustawiony, więc możesz to sprawdzić za pomocą łańcucha warunkowego:

$ unset foo
$ [[ ${!foo[*]} ]]; echo $?
1
$ foo=
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=bar
$ [[ ${!foo[*]} ]]; echo $?
0
$ foo=(bar baz)
$ [[ ${!foo[*]} ]]; echo $?
0

powinien być dostępny w dowolnej wersji basha> = 3.0

Aaron Davies
źródło
1

nie rzucić tego roweru jeszcze bardziej, ale chciałem dodać

shopt -s -o nounset

to coś, co możesz dodać na początku skryptu, co spowoduje błąd, jeśli zmienne nie zostaną zadeklarowane w żadnym miejscu skryptu. Wiadomość, którą zobaczysz, to unbound variable, ale jak wspominają inni, nie złapie pustego ciągu lub wartości null. Aby upewnić się, że żadna indywidualna wartość nie jest pusta, możemy przetestować zmienną, gdy jest rozszerzana ${mystr:?}, znaną również jako rozwinięcie znaku dolara, co spowoduje błąd parameter null or not set.

Świętokradcze
źródło
1

Podręcznik Bash Reference Manual jest wiarygodnym źródłem informacji o bashu.

Oto przykład testowania zmiennej, aby sprawdzić, czy istnieje:

if [ -z "$PS1" ]; then
        echo This shell is not interactive
else
        echo This shell is interactive
fi

(Od sekcji 6.3.2 .)

Zauważ, że spacje po otwarte [i zanim ]to nie opcja .


Wskazówki dla użytkowników Vima

Miałem skrypt, który miał kilka deklaracji w następujący sposób:

export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"

Ale chciałem, żeby trzymali się wszelkich istniejących wartości. Więc przepisałem je tak, aby wyglądały tak:

if [ -z "$VARIABLE_NAME" ]; then
        export VARIABLE_NAME="$SOME_OTHER_VARIABLE/path-part"
fi

Udało mi się zautomatyzować to w vimie za pomocą szybkiego wyrażenia regularnego:

s/\vexport ([A-Z_]+)\=("[^"]+")\n/if [ -z "$\1" ]; then\r  export \1=\2\rfi\r/gc

Można to zastosować, wybierając wizualnie odpowiednie linie, a następnie wpisując :. Pasek poleceń wypełnia się wstępnie :'<,'>. Wklej powyższe polecenie i naciśnij Enter.


Testowane na tej wersji Vima:

VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Aug 22 2015 15:38:58)
Compiled by root@apple.com

Użytkownicy systemu Windows mogą chcieć różnych zakończeń linii.

funroll
źródło
0

Oto, co moim zdaniem jest znacznie jaśniejszym sposobem sprawdzenia, czy zmienna jest zdefiniowana:

var_defined() {
    local var_name=$1
    set | grep "^${var_name}=" 1>/dev/null
    return $?
}

Użyj go w następujący sposób:

if var_defined foo; then
    echo "foo is defined"
else
    echo "foo is not defined"
fi
szwajcarski
źródło
1
Pisanie funkcji nie jest złym pomysłem; wzywanie grepjest okropne. Zauważ, że bashobsługuje ${!var_name}jako sposób na uzyskanie wartości zmiennej, której nazwa jest określona w $var_name, więc name=value; value=1; echo ${name} ${!name}daje value 1.
Jonathan Leffler
0

Krótszą wersją do testowania niezdefiniowanej zmiennej może być po prostu:

test -z ${mystr} && echo "mystr is not defined"
nfleury
źródło
-3

wywołanie ustawione bez żadnych argumentów .. wypisuje wszystkie zdefiniowane zmienne ..
ostatnie na liście byłyby tymi zdefiniowanymi w twoim skrypcie ..
więc możesz potokować jego wyjście do czegoś, co mogłoby dowiedzieć się, jakie rzeczy są zdefiniowane, a co nie

Aditya Mukherji
źródło
2
Nie przegłosowałem tego, ale to coś w rodzaju młota kowalskiego, aby złamać raczej mały orzech.
Jonathan Leffler,
Właściwie podoba mi się ta odpowiedź bardziej niż zaakceptowana odpowiedź. Jasne jest, co się dzieje w tej odpowiedzi. Przyjęta odpowiedź jest cholernie chwiejna.
Szwajcaria
Wygląda na to, że ta odpowiedź jest bardziej efektem ubocznym wdrożenia „zestawu” niż czymś, co jest pożądane.
Jonathan Morgan