Jak mogę zadeklarować i używać zmiennych logicznych w skrypcie powłoki?

977

Próbowałem zadeklarować zmienną logiczną w skrypcie powłoki przy użyciu następującej składni:

variable=$false

variable=$true

Czy to jest poprawne? Ponadto, jeśli chciałbym zaktualizować tę zmienną, czy użyłbym tej samej składni? Wreszcie, czy następująca składnia do używania zmiennych boolowskich jako wyrażeń jest poprawna?

if [ $variable ]

if [ !$variable ]
hassaanm
źródło
73
STRZEC SIĘ! trueoraz falsew kontekście większości fragmentów poniżej są po prostu ciągi, a nie to bash built-ins!!! Przeczytaj odpowiedź Mike'a Holta poniżej. (Jest to jeden z przykładów, w których wysoko głosowana i zaakceptowana odpowiedź jest myląca i
prześladuje
7
@mjv Większość nieporozumień związanych z tym pytaniem (i odpowiedzią Miku) wynikała z faktu, że Miku zrewidował swoją odpowiedź w pewnym momencie po opublikowaniu kilku komentarzy opisujących, w jaki sposób odpowiedź Miku wymagała wywołania wbudowanej bash true. Okazuje się, że oryginalna odpowiedź Miku naprawdę wywołała truewbudowane, ale poprawiona odpowiedź nie. To spowodowało, że wspomniane komentarze wydają się błędne na temat działania kodu Miku. Od tamtej pory odpowiedź Miku została zredagowana, aby wyraźnie pokazywać zarówno oryginalny, jak i poprawiony kod. Mam nadzieję, że to raz na zawsze uspokoi zamieszanie.
Mike Holt,
2
[ true ] && echo yes, true is truei (upppsss) [ false ] && echo yes, false is also true. / bin / true i / bin / false daje kod powrotu $? dla funkcji nie do porównania.
fcm
Jeśli zmienna jest ustawiona, variable=somethingto jest prawdą, jeśli nieokreślone variable=, byłoby to fałszem [[ $variable ]] && echo true || echo falsei odwróceniem[[ ! $variable ]] && echo false || echo true
Ivan

Odpowiedzi:

1200

Poprawiona odpowiedź (12 lutego 2014 r.)

the_world_is_flat=true
# ...do something interesting...
if [ "$the_world_is_flat" = true ] ; then
    echo 'Be careful not to fall off!'
fi

Oryginalna odpowiedź

Ostrzeżenia: https://stackoverflow.com/a/21210966/89391

the_world_is_flat=true
# ...do something interesting...
if $the_world_is_flat ; then
    echo 'Be careful not to fall off!'
fi

Od: Używanie zmiennych boolowskich w Bash

Powodem zamieszczenia oryginalnej odpowiedzi jest to, że komentarze przed wersją z 12 lutego 2014 r. Odnoszą się tylko do oryginalnej odpowiedzi, a wiele komentarzy jest błędnych, gdy są powiązane ze zmienioną odpowiedzią. Na przykład komentarz Dennisa Williamsona dotyczący wbudowanego basha true2 czerwca 2010 r. Dotyczy tylko oryginalnej odpowiedzi, a nie poprawionej.

miku
źródło
37
Aby wyjaśnić, co się dzieje: ifinstrukcja wykonuje zawartość zmiennej, która jest wbudowana w Bash true. Każde polecenie można ustawić jako wartość zmiennej, a jego wartość wyjściowa zostanie oszacowana.
Wstrzymano do odwołania.
7
@pms Operatory „-o” i „-a” dotyczą tylko polecenia „test” (alias „[]”). Zamiast tego jest to polecenie „if +” bez „testu”. (Na przykład „if grep foo file, then ...”.) Więc użyj normalnego operatora &&i ||operatorów: # t1=true; t2=true; f1=false;# if $t1 || $f1; then echo is_true ; else echo is_false; fi; (zwraca „true”, ponieważ t1 = true) # if $t1 && $f1 || $t2; then echo is_true ; else echo is_false; fi (zwraca „true”, ponieważ t2 = true) . Ponownie działa to TYLKO, ponieważ „prawda” / „fałsz” są wbudowanymi funkcjami bash (zwracają wartość prawda / fałsz). Nie możesz użyć „if $ var ...”, chyba że var jest cmd (tj. Prawda lub fałsz)
Michael
14
-1, zobacz moją odpowiedź na wyjaśnienie.
Dennis
3
Wiele niepoprawnych informacji tutaj. / bin / true nie jest efektywnie używany. Zobacz odpowiedź Dennisa.
ajk
1
Ten kod nie jest taki sam i nie działa w taki sam sposób, jak linkowany artykuł. Połączony kod wywołuje program o nazwie zapisanej w zmiennej, ale kod w tej odpowiedzi jest jedynie ciągiem znaków.
Pytania do kolonela
794

TL; DR

bool=true

if [ "$bool" = true ]

Problemy z ( oryginalną ) odpowiedzią Miku

Ja nie polecam zaakceptowane odpowiedź 1 . Jego składnia jest ładna, ale ma pewne wady.

Powiedzmy, że mamy następujący warunek.

if $var; then
  echo 'Muahahaha!'
fi

W następujących przypadkach 2 warunek ten oceni się jako prawdziwy i wykona polecenie zagnieżdżone.

# Variable var not defined beforehand. Case 1
var=''  # Equivalent to var="".        Case 2
var=    #                              Case 3
unset var  #                           Case 4
var='<some valid command>'  #          Case 5

Zwykle chcesz, aby warunek był oceniany na true, gdy zmienna „Boolean” varw tym przykładzie jest jawnie ustawiona na true. Wszystkie pozostałe przypadki są niebezpiecznie wprowadzające w błąd!

Ostatni przypadek (# 5) jest szczególnie niegrzeczny, ponieważ wykona polecenie zawarte w zmiennej (dlatego warunek ocenia się na true dla prawidłowych poleceń 3, 4 ).

Oto nieszkodliwy przykład:

var='echo this text will be displayed when the condition is evaluated'
if $var; then
  echo 'Muahahaha!'
fi

# Outputs:
# this text will be displayed when the condition is evaluated
# Muahahaha!

Podawanie zmiennych jest bezpieczniejsze, np if "$var"; then. W powyższych przypadkach powinieneś otrzymać ostrzeżenie, że polecenia nie znaleziono. Ale nadal możemy zrobić lepiej (patrz moje rekomendacje na dole).

Zobacz także wyjaśnienie Mike'a Holta oryginalnej odpowiedzi Miku.

Problemy z odpowiedzią Hbar

Takie podejście ma również nieoczekiwane zachowanie.

var=false
if [ $var ]; then
  echo "This won't print, var is false!"
fi

# Outputs:
# This won't print, var is false!

Można oczekiwać, że powyższy warunek będzie miał wartość false, dlatego nigdy nie będzie wykonywana instrukcja zagnieżdżona. Niespodzianka!

Cytując wartość ( "false"), cytując zmienną ( "$var") lub używając testlub [[zamiast [, nie robi różnicy.

Co ja nie polecam:

Oto sposoby, które polecam sprawdzić swoje „booleany”. Działają zgodnie z oczekiwaniami.

bool=true

if [ "$bool" = true ]; then
if [ "$bool" = "true" ]; then

if [[ "$bool" = true ]]; then
if [[ "$bool" = "true" ]]; then
if [[ "$bool" == true ]]; then
if [[ "$bool" == "true" ]]; then

if test "$bool" = true; then
if test "$bool" = "true"; then

Wszystkie są prawie równoważne. Będziesz musiał wpisać kilka dodatkowych naciśnięć klawiszy niż podejścia w pozostałych odpowiedziach 5 , ale twój kod będzie bardziej defensywny.


Przypisy

  1. Odpowiedź Miku została zredagowana i nie zawiera już (znanych) wad.
  2. Nie wyczerpująca lista.
  3. Prawidłowe polecenie w tym kontekście oznacza polecenie, które istnieje. Nie ma znaczenia, czy polecenie jest używane poprawnie czy niepoprawnie. Np. man womanNadal byłoby uważane za prawidłowe polecenie, nawet jeśli taka strona podręcznika nie istnieje.
  4. W przypadku nieprawidłowych (nieistniejących) poleceń Bash po prostu narzeka, że ​​polecenie nie zostało znalezione.
  5. Jeśli zależy Ci na długości, pierwsza rekomendacja jest najkrótsza.
Dennis
źródło
8
Używanie ==z [lub testnie jest przenośne. Biorąc pod uwagę przenośność jest jedyną zaletą [/ testma ponad [[, trzymaj się =.
chepner
2
@Scott Używam ryb jako mojej podstawowej powłoki, która ma rozsądny język skryptowy w porównaniu do bash.
Dennis
1
Tak, po prostu nie mogłem znaleźć w komentarzach uznania dla tego ukrytego żartu, więc musiałem to podkreślić =)
Kranach
5
Dla mnie koncepcyjnie łatwiej jest zrozumieć, jeśli użyję bool = "true". To jasne, że to tylko ciąg znaków, a nie jakaś specjalna wartość lub wbudowane.
wisbucky,
1
@dolmen absolutnie, ocena danych wejściowych nie jest tak ryzykowna, gdy kontrolujesz dane wejściowe, ale nadal uważam to za złą praktykę, której należy unikać, jeśli można jej łatwo uniknąć. Ktoś, kto widział tylko i używał poprzedniego stylu, może nie wiedzieć o jego wadach, które mogą powodować nieoczekiwane zachowanie.
Dennis
175

Wydaje się, że istnieje tu pewne nieporozumienie dotyczące wbudowanego Basha true, a dokładniej tego, w jaki sposób Bash rozwija i interpretuje wyrażenia w nawiasach.

Kod w odpowiedzi Miku ma absolutnie nic wspólnego z wbudowanego polecenia bash true, ani /bin/true, ani żaden inny smak truepolecenia. W tym przypadku truejest to zwykły ciąg znaków i nigdy nie jest wywoływane truepolecenie / polecenie wbudowane, ani przez przypisanie zmiennej, ani przez ocenę wyrażenia warunkowego.

Poniższy kod jest funkcjonalnie identyczny z kodem w odpowiedzi miku:

the_world_is_flat=yeah
if [ "$the_world_is_flat" = yeah ]; then
    echo 'Be careful not to fall off!'
fi

Tylko różnicą jest to, że cztery znaki są porównywane są „Tak”, „e”, „a” i „H”, a nie „t”, „R”, „u” i „E”. Otóż ​​to. Nie próbuje się wywoływać komendy ani wbudowanego programu o nazwie yeah, ani też (w przykładzie miku) nie ma żadnej specjalnej obsługi, gdy Bash analizuje token true. To tylko struna i całkowicie dowolna.

Aktualizacja (2014-02-19): Po podążeniu za linkiem w odpowiedzi miku, teraz widzę, skąd bierze się zamieszanie. Odpowiedź Miku używa pojedynczych nawiasów, ale fragment kodu, do którego prowadzi łącze, nie używa nawiasów. To poprostu:

the_world_is_flat=true
if $the_world_is_flat; then
  echo 'Be careful not to fall off!'
fi

Oba fragmenty kodu będą zachowywać się w ten sam sposób, ale nawiasy całkowicie zmieniają to, co dzieje się pod maską.

Oto, co robi Bash w każdym przypadku:

Bez nawiasów:

  1. Rozwiń zmienną $the_world_is_flatdo ciągu "true".
  2. Próba przetworzenia ciągu "true"jako polecenia.
  3. Znajdź i uruchom truepolecenie (wbudowane lub /bin/true, w zależności od wersji Bash).
  4. Porównaj kod wyjścia truepolecenia (który zawsze wynosi 0) z 0. Przypomnij sobie, że w większości powłok kod wyjścia 0 oznacza sukces, a cokolwiek innego wskazuje na niepowodzenie.
  5. Ponieważ kodem wyjścia było 0 (sukces), należy wykonać klauzulę ifinstrukcjithen

Wsporniki:

  1. Rozwiń zmienną $the_world_is_flatdo ciągu "true".
  2. Analizuj teraz w pełni rozwinięte wyrażenie warunkowe, które ma formę string1 = string2. =Operator bash męska porównanie ciąg operator. Więc...
  3. Wykonaj porównanie ciągów na "true"i "true".
  4. Tak, dwa ciągi były takie same, więc wartość warunku jest prawdziwa.
  5. Wykonaj klauzulę ifinstrukcji then.

Kod bez nawiasów działa, ponieważ truepolecenie zwraca kod wyjścia 0, co oznacza sukces. Kod w nawiasach działa, ponieważ wartość $the_world_is_flatjest identyczna z literałem łańcucha truepo prawej stronie =.

Aby doprowadzić punkt do domu, rozważ następujące dwa fragmenty kodu:

Ten kod (jeśli jest uruchamiany z uprawnieniami administratora) uruchomi ponownie komputer:

var=reboot
if $var; then
  echo 'Muahahaha! You are going down!'
fi

Ten kod po prostu wypisuje „Niezła próba”. Polecenie ponownego uruchomienia nie jest wywoływane.

var=reboot
if [ $var ]; then
  echo 'Nice try.'
fi

Aktualizacja (2014-04-14) Aby odpowiedzieć na pytanie w komentarzach dotyczące różnicy między =i ==: AFAIK, nie ma różnicy. ==Operator jest synonimem atakujących specyficzne dla =, io ile widziałem, one działają dokładnie tak samo we wszystkich kontekstach.

Należy jednak pamiętać, że jestem specjalnie mówić o =i ==operatorów porównania ciąg używany zarówno w [ ]lub [[ ]]testów. Nie sugeruję tego =i ==są one wymienne wszędzie w bash.

Na przykład, oczywiście nie możesz wykonać przypisania zmiennej ==, na przykład var=="foo"(cóż, technicznie możesz to zrobić, ale wartość varbędzie "=foo", ponieważ Bash nie widzi ==tutaj operatora, widzi =operatora (przypisanie), po którym następuje dosłowna wartość ="foo", która właśnie się staje "=foo").

Ponadto, chociaż =i ==są wymienne, należy pamiętać, że jak te testy praca nie zależy od tego, czy używasz go wewnątrz [ ]lub [[ ]], a także od tego, czy argumenty są notowane. Możesz przeczytać więcej na ten temat w Advanced Bash Scripting Guide: 7.3 Inne operatory porównania (przewiń w dół do dyskusji na temat =i ==).

Mike Holt
źródło
Podejście bez nawiasów ma również tę zaletę, że pozwala pisać czyste, wyraźne (imo) one-linery, takie jak$the_world_is_flat && echo "you are in flatland!"
ajk
9
Prawdziwe. Chociaż nie jestem zwolennikiem takiego podejścia. Chciałem tylko wyjaśnić niektóre z dezinformacji, które są tutaj przegłosowane, aby ludzie, którzy natkną się na ten temat później, nie odejdą z mnóstwem nieporozumień na temat tego, jak to wszystko działa.
Mike Holt
1
Powodem zamieszania jest to, że oryginalna odpowiedź miku trwała 4 lata. Wszystkie odniesienia do wbudowanej wersji truedotyczyły pierwotnej odpowiedzi. (Zmieniona odpowiedź z 12 lutego 2014 r. Nie została przesłana przez miku.) Zredagowałem odpowiedź, aby była zarówno oryginalna, jak i poprawiona. Wtedy komentarze ludzi mają sens.
wisbucky,
1
Po przeczytaniu przedstawionych tutaj odpowiedzi mam wrażenie, że nie ma czegoś takiego jak użycie prawdziwego true. Czy jest jakiś sposób? Podejrzewam, że wielu programistów, którzy są przyzwyczajeni do bardziej rygorystycznych języków, przeglądając tę ​​odpowiedź, aby pomóc im w pomieszaniu bashkleju, aby ich życie było nieco łatwiejsze, chciałby ===operatora, aby ciągi znaków i „logiczne” nie były w rzeczywistości wymienne. Należy po prostu trzymać się od 0 do 1 i użytkowania (( $maybeIAmTrue ))jak zasugerowano w Quolonel pytań jest odpowiedź ?
SeldomNeedy
2
Aby odpowiedzieć na komentarz SeldomNeedy, tak, możesz użyć rzeczywistej true, ale ogólnie nie jako czegoś do porównania zmiennej, ponieważ rzeczywista truenie ma wartości jako takiej. Wystarczy ustawić status wyjścia na 0, wskazując na sukces. Warto zauważyć, że jest to zasadniczo odpowiednik tak zwanego „polecenia zerowego” lub :. Jeśli chodzi o używanie 0i 1, to właśnie robię we wszystkich moich skryptach w dzisiejszych czasach, w których potrzebuję booleanów. I używam (( ))operatora zamiast [[ ]]do oceny. Na przykład, jeśli mam flag=0, mogę to zrobićif (( flag )); then ...
Mike Holt,
57

Użyj wyrażeń arytmetycznych.

#!/bin/bash

false=0
true=1

((false)) && echo false
((true)) && echo true
((!false)) && echo not false
((!true)) && echo not true

Wynik:

prawda,
nie fałsz

Pytania o Kwolonel
źródło
3
zalety: (1.) zachowanie jest podobne do sposobu obsługi booli przez C, (2.) składnia jest bardzo zwięzła / minimalna (nie wymaga zmiennej po prawej stronie i operatorów takich jak „=” lub „==”), (3) .) <subiektywny> dla mnie Rozumiem, co dzieje się bez długiego, skomplikowanego wyjaśnienia ... kontrast z odpowiedziami Miku i Dennisa, które wydają się wymagać długich, skomplikowanych wyjaśnień </subjective>
Trevor Boyd Smith
3
@TrevorBoydSmith Dlaczego nie powiedziałeś po prostu: „plusy: wszystko, minusy: nic”. Pozwoli to zaoszczędzić koszty amortyzacji na klawiaturze i monitorze w dłuższej perspektywie.
Pytania do kolonistów
4
Do użytku interaktywnego, takiego jak jednowarstwowe, pamiętaj, aby zostawić po nim miejsce !, w przeciwnym razie spowoduje to rozszerzenie historii. ((! foo))działa, tak też działa ! ((foo)). Uwielbiam to rozwiązanie, BTW. Wreszcie zwięzły sposób wykonywania zmiennych boolowskich. ((foo || bar))działa zgodnie z oczekiwaniami.
Peter Cordes,
5
(())rozwija rekurencyjnie zmienne, czego się nie spodziewałem. foo=bar; bar=baz; ((foo)) && echo echonic nie drukuje, ale to prawda baz=1. Możesz więc wspierać, foo=truea foo=falsetakże 0 lub 1, robiąc true=1.
Peter Cordes,
2
@quolonel dziękuję za bardzo pomocny zasób. Oczywiście moje rozumienie jest ograniczone - ludzką naturą jest ograniczenie wszelkiego zrozumienia bez względu na dziedzinę. Czy mógłbyś mi jednak powiedzieć, które z moich stwierdzeń prowadzi do założenia, że ​​moje rozumienie tej konkretnej sprawy jest niepełne?
Hubert Grzeskowiak
34

Krótko mówiąc:

W Bash nie ma booleanów

Bash ma wyrażenia logiczne pod względem porównania i warunków. To powiedziawszy, to, co możesz zadeklarować i porównać w Bash, to ciągi i liczby. Otóż ​​to.

Gdziekolwiek zobaczysz truelub falsew Bash, jest to albo ciąg znaków, albo polecenie / wbudowane, które jest używane tylko do kodu wyjścia.

Ta składnia ...

if true; then ...

jest zasadniczo ...

if COMMAND; then ...

Warunek ten jest spełniony za każdym razem, gdy polecenie zwraca kod zakończenia 0 truei falsesą wbudowanymi funkcjami Bash, a czasem także samodzielnymi programami, które nie robią nic poza zwracaniem odpowiedniego kodu wyjścia.

Powyższy warunek jest równoważny:

COMMAND && ...

Korzystając z nawiasów kwadratowych lub testpolecenia, korzystasz z kodu wyjścia tej konstrukcji. Należy pamiętać, że [ ]i [[ ]]to również tylko polecenia / builtins jak każda inna. Więc ...

if [[ 1 == 1 ]]; then echo yes; fi

koresponduje z

if COMMAND; then echo yes; fi

i COMMANDtutaj jest[[ 1 == 1 ]]

if..then..fiKonstrukcja jest po prostu cukier syntaktyczny. Zawsze możesz po prostu uruchomić polecenia oddzielone podwójnym znakiem ampersand dla tego samego efektu:

[[ 1 == 1 ]] && echo yes

Podczas używania trueiw falsetych konstrukcjach testowych w rzeczywistości przekazujesz tylko ciąg znaków "true"lub "false"polecenie testowe. Oto przykład:

Wierzcie lub nie, ale wszystkie te warunki dają ten sam wynik :

if [[ false ]]; then ...
if [[ "false" ]]; then ...
if [[ true ]]; then ...
if [[ "true" ]]; then ...

TL; DR; zawsze porównuj z łańcuchami lub liczbami

Aby wyjaśnić to przyszłym czytelnikom, polecam zawsze stosowanie cytatów truei false:

ZROBIĆ

if [[ "${var}" == "true" ]]; then ...
if [[ "${var}" == "false" ]]; then ...
if [[ -n "${var:-}" ]]; then echo "var is not empty" ...

NIE

if [ ... ]; then ...  # Always use double square brackets in bash!
if [[ "${var}" ]]; then ...  # This is not as clear or searchable as -n
if [[ "${var}" != true ]]; then ...  # Creates impression of Booleans
if [[ "${var}" -eq "true" ]]; then ...  # `-eq` is for numbers and doesn't read as easy as `==`

Może

if [[ "${var}" != "true" ]]; then ...  # Creates impression of Booleans. It can be used for strict checking of dangerous operations. This condition is false for anything but the literal string "true".
Hubert Grzeskowiak
źródło
Wolę używać Ti Fwyjaśnić, że nie są to prawdziwe wartości logiczne.
phk
1
Nie mogę się zgodzić z „zawsze używaj podwójnych nawiasów w bash”. W rzeczywistości w prawie wszystkich skryptach, które napisałem, używam pojedynczych nawiasów, chyba że muszę dopasować wzór. Myślę, że należy zrozumieć różnicę między [(tj. test) [[I użyć tej, która jest odpowiednia do jego potrzeb.
Weijun Zhou
@WeijunZhou zastanawia się, w jakich przypadkach pojedyncze nawiasy są lepsze?
Hubert Grzeskowiak
To bardziej osobisty gust, po prostu uważam, że jest zbyt odważny, by powiedzieć „Zawsze używaj podwójnych nawiasów kwadratowych w bash”. Ale są pewne przypadki krawędzi, których użyłem. Pojedyncze nawiasy kwadratowe pozwalają określić sam test w var. Jako uproszczony przykład rozważmyif ....; then mytest='-gt'; else mytest='-eq'; fi; #several lines of code; if [ "$var1" "$mytest" "$var2" ]; then ...; fi
Weijun Zhou
@WeijunZhou Twój przykład jest silnym argumentem przeciwko pojedynczym nawiasom kwadratowym. Sprawia, że ​​kod jest znacznie trudniejszy do zrozumienia i otwiera okno szeroko otwarte na błędy. Podwójne nawiasy kwadratowe są bardziej rygorystyczne i zachęcają do czyszczenia kodu.
Hubert Grzeskowiak
18

Dawno, dawno temu, gdy wszystko, co mieliśmy sh, obsłużyliśmy Booleansa, polegając na konwencji testprogramu, w której testzwraca fałszywy status wyjścia, jeśli jest uruchamiany bez żadnych argumentów.

Pozwala to myśleć o zmiennej, która nie jest ustawiona jako fałsz, a zmienna ustawiona na dowolną wartość jako prawda. Dzisiaj testjest wbudowany w Bash i jest powszechnie znany z jednoznakowego aliasu [(lub pliku wykonywalnego do użycia w powłokach, które go nie mają, jak zauważa dolmen):

FLAG="up or <set>"

if [ "$FLAG" ] ; then
    echo 'Is true'
else
    echo 'Is false'
fi

# Unset FLAG
#    also works
FLAG=

if [ "$FLAG" ] ; then
    echo 'Continues true'
else
    echo 'Turned false'
fi

Ze względu na konwencje cytowania autorzy skryptów wolą używać [[naśladującego polecenia złożonego test, ale mają ładniejszą składnię: zmienne ze spacjami nie muszą być cytowane; można używać &&i ||jako operatorów logicznych o dziwnym pierwszeństwie, a liczba terminów nie ma ograniczeń POSIX.

Na przykład, aby ustalić, czy FLAG jest ustawiona, a COUNT jest liczbą większą niż 1:

FLAG="u p"
COUNT=3

if [[ $FLAG  && $COUNT -gt '1' ]] ; then
    echo 'Flag up, count bigger than 1'
else
    echo 'Nope'
fi

Te rzeczy mogą być mylące, gdy potrzebne są spacje, ciągi zerowe i zmienne zerowe, a także, gdy skrypt musi pracować z kilkoma powłokami.

Hbar
źródło
3
[to nie tylko alias w środku bash. Ten alias istnieje również jako plik binarny (lub jako link prowadzący do) i może być używany z gołą wersją sh. Sprawdzić ls -l /usr/bin/\[. Z bash/ zshpowinieneś zamiast tego użyć [[tego, co jest prawdziwie czysto wewnętrzne i jest znacznie potężniejsze.
dolmen
1
@dolmen, [a testtakże Bash SHELL BUILTIN COMMAND według strony podręcznika Bash, więc nie powinno być problemu z wydajnością. To samo z np. Dash. (/ bin / sh może być tylko dowiązaniem symbolicznym do / bin / dash). Aby użyć pliku wykonywalnego, musisz użyć pełnej ścieżki, tj /usr/bin/\[.
jarno
12

Jak mogę zadeklarować i używać zmiennych logicznych w skrypcie powłoki?

W przeciwieństwie do wielu innych języków programowania, Bash nie segreguje swoich zmiennych według „typu”. [1]

Więc odpowiedź jest dość jasna. W Bash nie ma żadnej zmiennej boolowskiej .

Jednak:

Za pomocą instrukcji deklaracji możemy ograniczyć przypisanie wartości do zmiennych. [2]

#!/bin/bash
declare -ir BOOL=(0 1) # Remember BOOL can't be unset till this shell terminates
readonly false=${BOOL[0]}
readonly true=${BOOL[1]}

# Same as declare -ir false=0 true=1
((true)) && echo "True"
((false)) && echo "False"
((!true)) && echo "Not True"
((!false)) && echo "Not false"

rOpcja w declarei readonlysłuży do stanu wyraźnie, że zmienne są tylko do odczytu . Mam nadzieję, że cel jest jasny.

sjsam
źródło
1
Dlaczego po prostu nie robisz declare -ir false=0 true=1? Jaka jest zaleta korzystania z tablicy?
Benjamin W.
@BenjaminW. Chciałem tylko wspomnieć o ropcji i readonlypoleceniu. Zrobiłbym to tak, jak zasugerowałeś w moich skryptach
sjsam 30.04.16
może coś mi umknęło, ale dlaczego prawda i fałsz deklarowane w ten sposób nie używają znaku dolara? $ true $ false
qodeninja
Dosłownie po prostu kopiuję moją odpowiedź i ją pogarszam.
Pytania do Kwolonela
@QuolonelQuestions Zmienne Bash nie są wpisywane , dlatego nie ma sensu mówić. declare and use boolean variablesMożemy po prostu na wiele sposobów naśladować / zakładać, że zmienna ma typ . Nigdzie nie wspomniałem o tym w twojej odpowiedzi.
sjsam
10

Zamiast udawać Boolean i pozostawiać pułapkę dla przyszłych czytelników, dlaczego nie po prostu użyć lepszej wartości niż prawda i fałsz?

Na przykład:

build_state=success
if something-horrible; then
  build_state=failed
fi

if [[ "$build_state" == success ]]; then
  echo go home; you are done
else
  echo your head is on fire; run around in circles
fi
Pirolistyczny
źródło
dlaczego nie liczby całkowite?
phil294
3
@Blauhirn, ponieważ liczby całkowite są używane w różny sposób w zależności od języka. W niektórych językach 0wymusza na falsei 1na true. Jeśli chodzi o kody wyjścia programu (które historycznie używa bash), to 0dla pozytywnego wyniku lub truewszystko inne jest negatywne / błąd lub false.
Hubert Grzeskowiak
7

POSIX (interfejs przenośnego systemu operacyjnego)

Brakuje mi tutaj kluczowej kwestii, którą jest przenośność. Dlatego mój nagłówek ma w sobie POSIX .

Zasadniczo wszystkie głosowane odpowiedzi są poprawne, z wyjątkiem tego, że są zbyt specyficzne dla Bash .

Zasadniczo chcę tylko dodać więcej informacji na temat przenośności.


  1. [i ]nawiasy jak w [ "$var" = true ]nie są konieczne, możesz je pominąć i testbezpośrednio użyć polecenia:

    test "$var" = true && yourCodeIfTrue || yourCodeIfFalse

    Ważna uwaga: nie zalecam już tego, ponieważ jest on powoli przestarzały i trudniej jest łączyć wiele instrukcji.

  2. Wyobraźmy sobie, co te słowa truei falsemyśli do powłoki, test to sam:

    echo $(( true ))
    0
    echo $(( false ))
    1

    Ale używając cytatów:

    echo $(( "true" ))
    bash: "true": syntax error: operand expected (error token is ""true"")
    sh (dash): sh: 1: arithmetic expression: expecting primary: ""true""

    To samo dotyczy:

    echo $(( "false" ))

    Powłoka nie może interpretować tego inaczej niż łańcucha. Mam nadzieję, że rozumiesz, jak dobrze jest używać właściwego słowa kluczowego bez cudzysłowów .

    Ale nikt nie powiedział tego w poprzednich odpowiedziach.

  3. Co to znaczy? Cóż, kilka rzeczy.

    • Powinieneś się przyzwyczaić do boolowskich słów kluczowych, które w rzeczywistości są traktowane jak liczby, to znaczy true= 0i false=1 , pamiętaj, że wszystkie niezerowe wartości są traktowane jak false.

    • Ponieważ są one traktowane jako liczby, należy je również traktować w ten sposób, tzn. Jeśli zdefiniujesz zmienną, powiedz:

      var_bool=true
      echo "$var_bool"
       true

      możesz stworzyć jego przeciwną wartość za pomocą:

      var_bool=$(( 1 - $var_bool ))  # same as $(( ! $var_bool ))
      echo "$var_bool"
      1

    Jak widać, powłoka drukuje trueciąg znaków po raz pierwszy, ale od tego czasu wszystko działa odpowiednio za pomocą liczb 0reprezentujących truelub 1reprezentujących false.


Wreszcie, co powinieneś zrobić z tymi wszystkimi informacjami

  • Po pierwsze, jeden dobry nawyk byłby przypisywany 0zamiast true; 1zamiast false.

  • Drugim dobrym nawykiem byłoby testowanie, czy zmienna jest / nie jest równa zero:

    if [ "$var_bool" -eq 0 ]; then
         yourCodeIfTrue
    else
         yourCodeIfFalse
    fi
LinuxSecurityFreak
źródło
6

Jeśli chodzi o składnię, jest to prosta metodologia, której używam (na przykład) do konsekwentnego i rozsądnego zarządzania logiką logiczną:

# Tests
var=
var=''
var=""
var=0
var=1
var="abc"
var=abc

if [[ -n "${var}" ]] ; then
    echo 'true'
fi
if [[ -z "${var}" ]] ; then
    echo 'false'
fi

# Results
# var=        # false
# var=''      # false
# var=""      # false
# var=0       # true
# var=1       # true
# var="abc"   # true
# var=abc     # true

Jeśli zmienna nigdy nie jest zadeklarowana, odpowiedź brzmi: # false

Tak więc prostym sposobem na ustawienie zmiennej na true (przy użyciu tej metodologii składni) byłoby var=1:; odwrotnie var=''.

Odniesienie:

-n = Prawda, jeśli długość ciągu var jest różna od zera.

-z = Prawda, jeśli długość ciągu var wynosi zero.

Eric P.
źródło
5

W wielu językach programowania typ boolowski jest lub jest zaimplementowany jako podtyp liczby całkowitej, gdzie truezachowuje się jak 1i falsezachowuje się jak 0:

Matematycznie algebra boolowska przypomina arytmetykę liczb całkowitych 2. Dlatego, jeśli język nie zapewnia natywnego typu logicznego, najbardziej naturalnym i wydajnym rozwiązaniem jest użycie liczb całkowitych. Działa z prawie każdym językiem. Na przykład w Bash możesz wykonać:

# val=1; ((val)) && echo "true" || echo "false"
true
# val=0; ((val)) && echo "true" || echo "false"
false

bash człowieka :

((wyrażenie))

Wyrażenie ocenia się zgodnie z zasadami opisanymi poniżej w części OCENA ARYTETYCZNA. Jeśli wartość wyrażenia jest różna od zera, zwracanym statusem jest 0; w przeciwnym razie zwracany jest status 1. Jest to dokładnie równoważne z wyrażeniem „wyrażenie”.

Cyker
źródło
5

Bill Parker zostaje odrzucony , ponieważ jego definicje są odwrócone od normalnej konwencji kodu. Zwykle prawda jest zdefiniowana jako 0, a fałsz jest niezerowa. 1 będzie działać na fałsz, podobnie jak 9999 i -1. To samo z wartościami zwracanymi przez funkcję - 0 oznacza sukces, a wszystko niezerowe oznacza niepowodzenie. Przepraszam, nie mam jeszcze wiarygodności ulicy, aby głosować lub odpowiadać mu bezpośrednio.

Bash zaleca stosowanie podwójnych nawiasów jako nawyku zamiast pojedynczych nawiasów, a link podany przez Mike'a Holta wyjaśnia różnice w ich działaniu. 7.3 Inne operatory porównania

Po pierwsze, -eqjest operatorem numerycznym, więc ma kod

#**** NOTE *** This gives error message *****
The_world_is_flat=0;
if [ "${The_world_is_flat}" -eq true ]; then

wyda instrukcję błędu, oczekując wyrażenia w postaci liczby całkowitej. Dotyczy to każdego parametru, ponieważ żadna z nich nie jest liczbą całkowitą. Jeśli jednak umieścimy wokół niego podwójne nawiasy klamrowe, nie wyda ono komunikatu o błędzie, ale da niepoprawną wartość (cóż, w 50% możliwych kombinacji). Będzie oceniać na [[0 -eq prawda]] = sukces, ale także na [[0 -eq fałsz]] = sukces, co jest błędne (hmmm .... co z tym, że to wbudowane ma wartość liczbową?).

#**** NOTE *** This gives wrong output *****
The_world_is_flat=true;
if [[ "${The_world_is_flat}" -eq true ]]; then

Istnieją inne kombinacje warunku, które również dają niepoprawne wyjście. Zasadniczo wszystko (oprócz warunku błędu wymienionego powyżej), które ustawia zmienną na wartość liczbową i porównuje ją z wbudowanym true / false lub ustawia zmienną na wbudowaną wartość true / false i porównuje ją z wartością liczbową. Ponadto wszystko, co ustawia zmienną na wbudowaną wartość prawda / fałsz i dokonuje porównania przy użyciu -eq. Unikaj więc -eqporównań boolowskich i unikaj używania wartości numerycznych do porównań boolowskich. Oto podsumowanie permutacji, które dadzą nieprawidłowe wyniki:

# With variable set as an integer and evaluating to true/false
# *** This will issue error warning and not run: *****
The_world_is_flat=0;
if [ "${The_world_is_flat}" -eq true ]; then

# With variable set as an integer and evaluating to true/false
# *** These statements will not evaluate properly: *****
The_world_is_flat=0;
if [ "${The_world_is_flat}" -eq true ]; then
#
if [[ "${The_world_is_flat}" -eq true ]]; then
#
if [ "${The_world_is_flat}" = true ]; then
#
if [[ "${The_world_is_flat}" = true ]]; then
#
if [ "${The_world_is_flat}" == true ]; then
#
if [[ "${The_world_is_flat}" == true ]]; then


# With variable set as an true/false builtin and evaluating to true/false
# *** These statements will not evaluate properly: *****
The_world_is_flat=true;
if [[ "${The_world_is_flat}" -eq true ]]; then
#
if [ "${The_world_is_flat}" = 0 ]; then
#
if [[ "${The_world_is_flat}" = 0 ]]; then
#
if [ "${The_world_is_flat}" == 0 ]; then
#
if [[ "${The_world_is_flat}" == 0 ]]; then

A teraz do tego, co działa. Używaj wbudowanych wartości prawda / fałsz do porównywania i oceny (jak zauważył Mike Hunt, nie umieszczaj ich w cudzysłowach). Następnie użyj znaku równości pojedynczej lub podwójnej (= lub ==) oraz nawiasów pojedynczych lub podwójnych ([] lub [[]]). Osobiście podoba mi się znak podwójnego znaku równości, ponieważ przypomina mi logiczne porównania w innych językach programowania i podwójne cudzysłowy tylko dlatego, że lubię pisać. Więc działają:

# With variable set as an integer and evaluating to true/false
# *** These statements will work properly: *****
#
The_world_is_flat=true/false;
if [ "${The_world_is_flat}" = true ]; then
#
if [[ "${The_world_is_flat}" = true ]]; then
#
if [ "${The_world_is_flat}" = true ]; then
#
if [[ "${The_world_is_flat}" == true ]]; then

Masz to.

Randyman99
źródło
2
W true/ falseZabudowy nie są tu stosowane (ignorować co podświetlanie składni niektórych redaktorów może sugerować), zwłaszcza w […]przypadkach można myśleć o nim jako prosty ciąg tutaj (jeden, który jest podany jako parametr do [komendy).
phk
Masz to teraz.
Peter Mortensen
4

Moje ustalenia i sugestie różnią się nieco od innych postów. Odkryłem, że mogę używać „booleanów” w zasadzie tak, jak w każdym „zwykłym” języku, bez sugerowania „skoku z obręczy” ...

Nie ma potrzeby []ani jawnych porównań ciągów ... Próbowałem wielu dystrybucji Linuksa. Testowałem Bash, Dash i BusyBox . Wyniki były zawsze takie same. Nie jestem pewien, o czym mówią oryginalne najpopularniejsze posty. Może czasy się zmieniły i to wszystko.

Jeśli ustawisz zmienną na true, będzie ona następnie oceniana jako „twierdząca” w ramach warunku. Ustaw go na false, a jego wartość będzie „negatywna”. Bardzo proste! Jedynym zastrzeżeniem jest to, że niezdefiniowana zmienna również ma wartość true ! Byłoby miło, gdyby zrobił coś przeciwnego (jak w większości języków), ale to jest sztuczka - wystarczy jawnie zainicjować swoje wartości logiczne na prawda lub fałsz .

Dlaczego to działa w ten sposób? Ta odpowiedź jest podwójna. A) prawda / fałsz w powłoce naprawdę oznacza „brak błędu” vs „błąd” (tj. 0 vs cokolwiek innego). B) prawda / fałsz nie są wartościami - są to raczej instrukcje w skryptach powłoki! Odnośnie do drugiego punktu, wykonanie truelub falsew linii samo ustawia wartość zwracaną dla bloku, w którym się znajdujesz, na tę wartość, tj. falseJest deklaracją „napotkanego błędu”, gdzie prawda „usuwa” to. Użycie go z przypisaniem do zmiennej „zwraca” to do zmiennej. An niezdefiniowanych zmiennych ocenia jak truew warunkowa dlatego, że równie oznacza 0 lub „nie napotkano błąd”.

Zobacz przykładowe linie Bash i wyniki poniżej. Sprawdź to sam, jeśli chcesz potwierdzić ...

#!/bin/sh

# Not yet defined...
echo "when set to ${myBool}"
if ${myBool}; then echo "it evaluates to true"; else echo "it evaluates to false"; fi;

myBool=true
echo "when set to ${myBool}"
if ${myBool}; then echo "it evaluates to true"; else echo "it evaluates to false"; fi;

myBool=false
echo "when set to ${myBool}"
if ${myBool}; then echo "it evaluates to true"; else echo "it evaluates to false"; fi;

Wydajność

when set to
it evaluates to true
when set to true
it evaluates to true
when set to false
it evaluates to false
BuvinJ
źródło
1

Oto prosty przykład, który działa dla mnie:

temp1=true
temp2=false

if [ "$temp1" = true ] || [ "$temp2" = true ]
then
    echo "Do something." 
else
    echo "Do something else."
fi
Harsimranjit Singh Kler
źródło
1

Oto implementacja krótkiego ręki if true.

# Function to test if a variable is set to "true"
_if () {
    [ "${1}" == "true" ] && return 0
    [ "${1}" == "True" ] && return 0
    [ "${1}" == "Yes" ] && return 0
    return 1
}

Przykład 1

my_boolean=true

_if ${my_boolean} && {
    echo "True Is True"
} || {
    echo "False Is False"
}

Przykład 2

my_boolean=false
! _if ${my_boolean} && echo "Not True is True"
Llundin
źródło
Tak, rozkład funkcjonalny jest niedoceniany.
Peter Mortensen
1

Stwierdziłem, że istniejące odpowiedzi są mylące.

Osobiście chcę po prostu mieć coś, co wygląda i działa jak C.

Ten fragment kodu działa wiele razy dziennie podczas produkcji:

snapshotEvents=true

if ($snapshotEvents)
then
    # Do stuff if true
fi

i aby wszyscy byli szczęśliwi, przetestowałem:

snapshotEvents=false

if !($snapshotEvents)
then
    # Do stuff if false
fi

Co również działało dobrze.

$snapshotEventsOcenia zawartość wartości zmiennej. Więc potrzebujesz $.

Tak naprawdę nie potrzebujesz nawiasów, po prostu uważam je za pomocne.

będzie
źródło
2
W przypadku usunięcia nawiasów jest to dokładnie oryginalna odpowiedź @ miku u góry.
dolmen
1
Bez nawiasów wyrażenie nie ocenia.
będzie
@ tak zrobi. Nie potrzebujesz ().
phil294
1
@Blauhirn ... Cześć, swoje komentarze oparłem na eksperymentach z GNU Bash na Linux Mint / Ubuntu PC. Prawdopodobnie masz rację w teorii () - nie są potrzebne. Moją jedyną odpowiedzią jest wypróbowanie. Wygląda na to, że zależy od wersji Basha, rzeczywistego wyrażenia lub kontekstu i tym podobnych.
będzie
1

Oto poprawka w stosunku do oryginalnej odpowiedzi miku, która rozwiązuje obawy Dennisa Williamsona dotyczące przypadku, w którym zmienna nie jest ustawiona:

the_world_is_flat=true

if ${the_world_is_flat:-false} ; then
    echo "Be careful not to fall off!"
fi

I sprawdzić, czy zmienna to false:

if ! ${the_world_is_flat:-false} ; then
    echo "Be careful not to fall off!"
fi

W przypadku innych przypadków z nieprzyjemną zawartością w zmiennej jest to problem z dowolnym zewnętrznym wejściem podawanym do programu.

Wszelkie dane zewnętrzne muszą zostać zweryfikowane przed zaufaniem. Ale weryfikacja ta musi zostać wykonana tylko raz, kiedy otrzyma się dane wejściowe.

Nie musi wpływać na wydajność programu, robiąc to przy każdym użyciu zmiennej, jak sugeruje Dennis Williamson .

dolmen
źródło
1

Możesz użyć shFlags .

Daje możliwość zdefiniowania: DEFINE_bool

Przykład:

DEFINE_bool(big_menu, true, "Include 'advanced' options in the menu listing");

W wierszu poleceń możesz zdefiniować:

sh script.sh --bigmenu
sh script.sh --nobigmenu # False
gogasca
źródło
GFlags nie ma sensu w tej odpowiedzi - jest to biblioteka C ++. Nie można go używać bezpośrednio w skryptach powłoki.
Jonathan Cross
Zaktualizowano odpowiedź na shFlags, który jest portem GFlags do powłoki.
gogasca
0

To jest test szybkości na różne sposoby testowania wartości „boolowskich” w Bash:

#!/bin/bash
rounds=100000

b=true # For true; b=false for false
type -a true
time for i in $(seq $rounds); do command $b; done
time for i in $(seq $rounds); do $b; done
time for i in $(seq $rounds); do [ "$b" == true ]; done
time for i in $(seq $rounds); do test "$b" == true; done
time for i in $(seq $rounds); do [[ $b == true ]]; done

b=x; # Or any non-null string for true; b='' for false
time for i in $(seq $rounds); do [ "$b" ]; done
time for i in $(seq $rounds); do [[ $b ]]; done

b=1 # Or any non-zero integer for true; b=0 for false
time for i in $(seq $rounds); do ((b)); done

Wydrukuje coś podobnego

true is a shell builtin
true is /bin/true

real    0m0,815s
user    0m0,767s
sys     0m0,029s

real    0m0,562s
user    0m0,509s
sys     0m0,022s

real    0m0,829s
user    0m0,782s
sys     0m0,008s

real    0m0,782s
user    0m0,730s
sys     0m0,015s

real    0m0,402s
user    0m0,391s
sys     0m0,006s

real    0m0,668s
user    0m0,633s
sys     0m0,008s

real    0m0,344s
user    0m0,311s
sys     0m0,016s

real    0m0,367s
user    0m0,347s
sys     0m0,017s
jarno
źródło
-2

Alternatywnie - użyj funkcji

is_ok(){ :;}
is_ok(){ return 1;}
is_ok && echo "It's OK" || echo "Something's wrong"

Zdefiniowanie funkcji jest mniej intuicyjne, ale sprawdzenie jej zwracanej wartości jest bardzo łatwe.

johnraff
źródło
1
Nie jest to zmienna, którą można przetestować, ale stała funkcja
jarno
@jarno Czy testowanie wartości zwracanej przez funkcję różni się od testowania zmiennej dla celów skryptu?
johnraff
Pytanie dotyczy zmiennych.
jarno
To prawda, chociaż użycie skryptu powłoki byłoby takie samo.
johnraff,
-2

Bash naprawdę myli problem z takimi [, [[, ((, $((, itd.

Wszystkie stąpają po sobie nawzajem w obszarach kodu. Myślę, że jest to głównie historia, w której Bash musiał udawać, że shczasami.

Przez większość czasu mogę po prostu wybrać metodę i trzymać się jej. W tym przypadku mam tendencję do deklarowania (najlepiej we wspólnym pliku biblioteki, z którym mogę dołączyć do .moich rzeczywistych skryptów).

TRUE=1; FALSE=0

Następnie mogę użyć ((... ))operatora arytmetycznego, aby w ten sposób przetestować.

testvar=$FALSE

if [[ -d ${does_directory_exist} ]]
then
    testvar=$TRUE;
fi

if (( testvar == TRUE )); then
    # Do stuff because the directory does exist
fi
  1. Musisz być zdyscyplinowany. Twój testvarmusi być ustawiony na $TRUElub $FALSEprzez cały czas.

  2. W ((... ))komparatorach nie potrzebujesz poprzedzającego $, co czyni go bardziej czytelnym.

  3. Mogę użyć ((... ))ponieważ $TRUE=1i $FALSE=0, tj. Wartości liczbowe.

  4. Minusem jest $okazjonalne stosowanie:

    testvar=$TRUE

    co nie jest takie ładne.

Nie jest to idealne rozwiązanie, ale obejmuje każdy przypadek, w którym potrzebuję takiego testu.

Bill Parker
źródło
2
Powinieneś zadeklarować swoje stałe tylko do odczytu. Zawsze też używaj nawiasów klamrowych podczas używania zmiennych. To konwencja, którą każdy powinien trzymać się IMHO. Dużym minusem tego rozwiązania jest to, że nie można mieszać wyrażenia algebraicznego z flagami testowymi lub porównaniami ciągów.
Hubert Grzeskowiak