Jak mogę sprawdzić, czy zmienna jest pusta lub zawiera tylko spacje?

240

Poniższa składnia bash sprawdza, czy paramnie jest pusta:

 [[ !  -z  $param  ]]

Na przykład:

param=""
[[ !  -z  $param  ]] && echo "I am not zero"

Brak wyników i jest w porządku.

Ale gdy paramjest pusty, z wyjątkiem jednego (lub więcej) znaków spacji, sprawa jest inna:

param=" " # one space
[[ !  -z  $param  ]] && echo "I am not zero"

Wyjście „Nie jestem zero”.

Jak mogę zmienić test, aby uznać zmienne zawierające tylko znaki spacji za puste?

maihabunash
źródło
4
Od man test: -z STRING - the length of STRING is zero. Jeśli chcesz usunąć wszystkie spacje $param, użyj${param// /}
dchirikov
6
WOW,trim() w żadnym * nixie nie ma żadnej prostej funkcji? Tyle różnych hacków, by osiągnąć coś tak prostego ...
ADTC
6
@ ADTC $ (sed / s \ ^ \ s + | \ s + $ // g '<<< $ string) wydaje mi się dość prosty. Po co wymyślać koło ponownie?
jbowman
3
Ponieważ może być
jaśniej
1
Wystarczy zauważyć, że [[ ! -z $param ]]jest to równoważne test ! -z $param.
Noah Sussman

Odpowiedzi:

292

Po pierwsze, zauważ, że -ztest jest wyraźnie dla:

długość łańcucha wynosi zero

Oznacza to, że łańcuch zawierający tylko spacje nie powinien być prawdziwy pod -z, ponieważ ma niezerową długość.

To, czego chcesz, to usunąć spacje ze zmiennej za pomocą rozszerzenia parametru zastępującego wzorzec :

[[ -z "${param// }" ]]

To rozszerza paramzmienną i zastępuje wszystkie dopasowania wzorca (pojedynczą spacją) niczym, więc ciąg zawierający tylko spacje zostanie rozwinięty do pustego ciągu.


Sedna jak to działa jest to, że ${var/pattern/string}zastępuje pierwszy najdłuższy mecz patternz string. Kiedy patternzaczyna się od /(jak wyżej), zastępuje wszystkie dopasowania. Ponieważ zamiana jest pusta, możemy pominąć końcową /i stringwartość:

$ {parametr / wzór / ciąg}

Wzór rozpręża się w celu wytworzenia wzoru jak w rozszerzania nazw. Parametr jest rozszerzany, a najdłuższe dopasowanie wzorca do jego wartości jest zastępowane ciągiem . Jeśli wzorzec zaczyna się od „/”, wszystkie dopasowania wzorca są zastępowane ciągiem . Zwykle tylko pierwszy mecz jest zastępowany. ... Jeśli łańcuch jest pusty, dopasowania wzorca są usuwane, a wzorzec / następujący może zostać pominięty.

Po tym wszystkim kończymy ${param// }na usuwaniu wszystkich spacji.

Zauważ, że chociaż jest obecny w ksh(skąd pochodzi), zshi bash, że składnia ta nie jest POSIX i nie powinna być używana w shskryptach.

Michael Homer
źródło
użyj sedpowiedzieli ... tylko echozmienną, którą powiedzieli ...
Mikezter
1
Hmm Jeśli tak, to ${var/pattern/string}czy nie powinno być [[ -z ${param/ /} ]]z odstępem między ukośnikami, który ma być wzorem, a niczym po łańcuchu?
Jesse Chisholm
@JesseChisholm "Ponieważ zamiana jest pusta, możemy pominąć końcową / i wartość ciągu"; „Jeśli łańcuch jest pusty, dopasowania wzorca są usuwane, a wzorzec / następujący można pominąć.”
Michael Homer
6
@MichaelHomer Odpowiedź na [[ -z "${param// }" ]]pewno wygląda na patternpustą i replacement stringpojedynczą spację. AH! Widzę to teraz if pattern begins with a slash, tęskniłem za tą częścią. więc wzór jest, / a łańcuch zostaje pominięty i dlatego jest pusty. Dzięki.
Jesse Chisholm,
uwaga bash: jeśli działa w zestawie -o rzeczownik (zestaw -u), a parametr jest nieustawiony (zamiast pustego lub wypełnionego spacją), spowoduje to wygenerowanie błędu zmiennej niezwiązanej. (set + u; .....) zwalnia ten test.
Brian Chrisman,
31

Najłatwiejszym sposobem sprawdzenia, czy łańcuch zawiera tylko znaki w autoryzowanym zestawie, jest sprawdzenie obecności nieautoryzowanych znaków. Dlatego zamiast sprawdzać, czy łańcuch zawiera tylko spacje, sprawdź, czy łańcuch zawiera znak inny niż spacja. W bash, ksh lub zsh:

if [[ $param = *[!\ ]* ]]; then
  echo "\$param contains characters other than space"
else
  echo "\$param consists of spaces only"
fi

„Składa się tylko ze spacji” obejmuje przypadek pustej (lub nieustawionej) zmiennej.

Możesz przetestować dowolny znak spacji. Użyj, [[ $param = *[^[:space:]]* ]]aby użyć ustawień regionalnych lub jakiejkolwiek wyraźnej listy białych znaków, które chcesz przetestować, np. [[ $param = *[$' \t\n']* ]]Aby sprawdzić spację, tabulator lub znak nowej linii.

Dopasowywanie łańcucha do wzorca z =wnętrzem [[ … ]]jest rozszerzeniem ksh (występującym również w bash i zsh). W dowolnym stylu Bourne / POSIX możesz użyć casekonstrukcji, aby dopasować ciąg do wzorca. Zauważ, że standardowe wzorce powłoki służą !do negowania zestawu znaków, a nie ^jak w większości składni wyrażeń regularnych.

case "$param" in
  *[!\ ]*) echo "\$param contains characters other than space";;
  *) echo "\$param consists of spaces only";;
esac

Aby przetestować znaki spacji, $'…'składnia jest specyficzna dla ksh / bash / zsh; możesz dosłownie wstawić te znaki w skrypcie (pamiętaj, że nowa linia musi znajdować się w cudzysłowach, ponieważ ukośnik + nowa linia rozwija się do zera), lub wygeneruj je, np.

whitespace=$(printf '\n\t ')
case "$param" in
  *[!$whitespace]*) echo "\$param contains non-whitespace characters";;
  *) echo "\$param consists of whitespace only";;
esac
Gilles
źródło
To prawie doskonałe - ale na razie nie odpowiada na zadane pytanie. Obecnie może odróżnić nie ustawioną lub pustą zmienną powłoki od zmiennej zawierającej tylko spacje. Jeśli zaakceptujesz moją sugestię, dodasz formularz rozwijania parametrów, który nie jest jeszcze zawężony przez żadną inną odpowiedź, która wyraźnie testuje zmienną powłoki pod kątem ustawienia - mam na myśli ${var?not set}- przekazanie tego testu i przetrwanie zapewni, że zmienna dopasowana przez ciebie *) casespowoduje ostateczne działanie na przykład odpowiedz.
mikeserv
Korekta - to w rzeczywistości, by odpowiedzieć na pytanie, pierwotnie zapytał, ale od tego czasu został stworzony w taki sposób, że całkowicie zmienia znaczenie kwestii i dlatego unieważnia swoją odpowiedź.
mikeserv
Trzeba [[ $param = *[!\ ]* ]]w mksh.
Stéphane Chazelas
20

POSIXly:

case $var in
  (*[![:blank:]]*) echo '$var contains non blank';;
  (*) echo '$var contains only blanks or is empty or unset'
esac

Aby rozróżnić puste , niepuste , puste , rozbrojone :

case ${var+x$var} in
  (x) echo empty;;
  ("") echo unset;;
  (x*[![:blank:]]*) echo non-blank;;
  (*) echo blank
esac

[:blank:]jest dla znaków odstępów w poziomie (spacja i tabulacja w ASCII, ale prawdopodobnie jest kilka innych w twoim języku; niektóre systemy będą zawierały spacje nierozdzielające (jeśli są dostępne), niektóre nie). Jeśli chcesz, jak również (jak znak nowej linii lub form-feed) pionowe znaki odstępów, wymienić [:blank:]z [:space:].

Stéphane Chazelas
źródło
POSIXly jest idealny, ponieważ będzie działał z (prawdopodobnie lepszym) myślnikiem.
Ken Sharp
zshwygląda na to, że ma z tym problem [![:blank:]], to przebicie :bjest nieprawidłowym modyfikatorem. W shi kshnaśladować, wzbudza to wydarzenie, którego nie znaleziono dla[
cuonglm
@cuonglm, co próbowałeś? var=foo zsh -c 'case ${var+x$var} in (x*[![:blank:]]*) echo x; esac'jest dla mnie OK. Nie znaleziono zdarzenia dotyczącego tylko interaktywnych powłok.
Stéphane Chazelas
@ StéphaneChazelas: Ach, tak, próbowałem z interaktywnymi powłokami. :bNieważny modyfikator jest nieco dziwne.
cuonglm
1
@FranklinYu na OS / X shjest bashzbudowany --enable-xpg-echo-defaulti --enable-strict-posix-defaulttak być zgodny z systemem UNIX (która polega na echo '\t'wysyłaniu kartę).
Stéphane Chazelas,
4

Jedynym pozostałym powodem do napisania skryptu powłoki, zamiast skryptu w dobrym języku skryptowym, jest to, że ekstremalna przenośność jest nadrzędnym problemem. Dziedzictwo /bin/shjest jedyną rzeczą, którą możesz mieć pewność, że masz, ale na przykład Perl jest bardziej dostępny na wiele platform niż Bash. Dlatego nigdy nie pisz skryptów powłoki, które używają funkcji, które nie są naprawdę uniwersalne - i pamiętaj, że kilku zastrzeżonych dostawców uniksowych zamroziło swoje środowisko powłoki przed POSIX.1-2001 .

Istnieje przenośny sposób na wykonanie tego testu, ale musisz użyć tr:

[ "x`printf '%s' "$var" | tr -d "$IFS"`" = x ]

( $IFSDogodnie domyślną wartością jest spacja, tabulator i nowa linia).

( printfWbudowany sam w sobie jest niesamowicie przenośny, ale poleganie na nim jest znacznie mniej kłopotliwe niż ustalenie, który wariant echomasz.)

zwol
źródło
1
To trochę skomplikowane. Zwykły przenośny sposób na sprawdzenie, czy ciąg znaków zawiera pewne jest zecase .
Gilles
@Gilles Nie sądzę, że można napisać caseglob, aby wykryć ciąg, który jest albo pusty, albo zawiera tylko białe znaki. (Byłoby to łatwe z wyrażeniem caseregularnym , ale nie robi wyrażeń regularnych. Konstrukt w odpowiedzi nie zadziała, jeśli zależy Ci na nowych wierszach.)
zwolnij
1
Podałem spacje jako przykład w mojej odpowiedzi, ponieważ o to właśnie pytałem. To równie łatwe do sprawdzenia dla białych znaków: case $param in *[![:space:]]*) …. Jeśli chcesz przetestować IFSznaki, możesz użyć wzorca *[$IFS]*.
Gilles
1
@mikeserv W naprawdę poważnie defensywnym skrypcie wyraźnie ustawiasz IFS <space><tab><newline>na początku, a następnie nigdy go nie dotykasz. Myślałem, że to rozproszy.
zwolnić
1
Jeśli chodzi o zmienne we wzorach, myślę, że działa to we wszystkich wersjach Bourne'a i wszystkich powłokach POSIX; wymagałoby to dodatkowego kodu do wykrycia wzorców wielkości liter i wyłączenia rozszerzenia zmiennych i nie mogę wymyślić żadnego powodu, aby to zrobić. Mam kilka takich przypadków, .profilektóre zostały wykonane przez wiele pocisków Bourne'a. Oczywiście [$IFS]nie zrobi tego, co było zamierzone, jeśli IFSma pewne wartości inne niż domyślne (puste (lub nieuzbrojone), zawierające ], zaczynające się od !lub ^).
Gilles
4
if [[ -n "${variable_name/[ ]*\n/}" ]]
then
    #execute if the the variable is not empty and contains non space characters
else
    #execute if the variable is empty or contains only spaces
fi
Guru
źródło
pozbywa się to każdego znaku białej spacji, a nie tylko pojedynczej spacji, prawda? dzięki
Alexander Mills,
0

Do testowania, czy zmienna jest pusta lub zawiera spacje, możesz również użyć tego kodu:

${name:?variable is empty}
pratik
źródło
2
Myślę, że twoja odpowiedź jest zaniżona, ponieważ „lub zawiera spacje” nie jest prawdziwe.
Bernhard
uważaj - to zabija bieżącą powłokę. Lepiej umieścić go w (: $ {subshell?}). Również - to napisze do stderr - prawdopodobnie chcesz przekierować. I najważniejsze, że ocenia rzeczywistą wartość zmiennej, jeśli nie jest ona nieustawiona lub zerowa. Jeśli jest tam polecenie, po prostu je uruchomiłeś. Najlepiej uruchomić ten test :.
mikeserv
jak to zabije pocisk. i możesz to przetestować, najpierw zdefiniuj, name=aaaaa następnie uruchom to polecenie echo ${name:?variable is empty}, wypisze wartość zmiennej nazwa, a teraz uruchom to polecenie echo ${name1:?variable is empty}, wypisze -sh: nazwa1: zmienna jest pusta
pratik
Tak - echo ${name:?variable is empty}albo oceni $namewartość inną niż null, albo zabije powłokę. Z tego samego powodu prompt > $randomvarnie powinieneś tego robić ${var?}- jeśli jest tam polecenie, prawdopodobnie zadziała - i nie wiesz, co to jest. Interaktywna powłoka nie musi wychodzić - dlatego twoja nie. Jeśli jednak chcesz zobaczyć jakieś testy, jest ich tutaj wiele . . Ten nie jest tak długi ...
mikeserv
0

Aby sprawdzić, czy zmienna nie jest ustawiona, pusta (ma wartość zerową) lub zawiera tylko spacje:

 [ -z "${param#"${param%%[! ]*}"}" ] && echo "empty"

Wyjaśnienie:

  • Rozwinięcie ${param%%[! ]*}usuwa od pierwszej spacji do końca.
  • To pozostawia tylko wiodące miejsca.
  • Następne rozszerzenie usuwa wiodące spacje ze zmiennej.
  • Jeśli wynik jest pusty, zmienna była na początek pusta.
  • Lub, jeśli zmienna nie była pusta, usunięcie spacji wiodących sprawiło, że była pusta.
  • Jeśli wynik jest pusty -z, echo empty.

Powyższe jest kompatybilne z POSIX i działa na każdym potomku Bourne'a.

Jeśli chcesz rozszerzyć to na karty, dołącz wyraźną kartę:

 [ -z "${param#"${param%%[!     ]*}"}" ] && echo "empty"

lub użyj zmiennej ze znakami, które chcesz usunąć:

 var=$' \t'   # in ksh, bash, zsh
 [ -z "${param#"${param%%[!$var]*}"}" ] && echo "empty"

lub możesz użyć „wyrażenia klasy znaków” POSIX (puste oznacza spację i tabulator):

 [ -z "${param#"${param%%[![:blank:]]*}"}" ] && echo "empty"

Ale to zawiedzie w mksh, lksh i posh.

Izaak
źródło
-1

Spróbuj tego:

$ [[ -z \`echo $n\` ]] && echo zero

zero
Hugo
źródło