Bash if [false]; zwraca prawdę

151

W tym tygodniu uczyłem się bash i wpadłem w kłopoty.

#!/bin/sh

if [ false ]; then
    echo "True"
else
    echo "False"
fi

To zawsze wyświetli wartość True, nawet jeśli warunek wydaje się wskazywać inaczej. Jak zdejmę nawiasy []to działa, ale nie rozumiem dlaczego.

tenmiles
źródło
3
Oto coś, od czego możesz zacząć.
keyser
10
Przy okazji, skrypt zaczynający się od #!/bin/shnie jest skryptem bash - to skrypt sh POSIX. Nawet jeśli interpreter POSIX sh w twoim systemie jest dostarczany przez bash, wyłącza on kilka rozszerzeń. Jeśli chcesz pisać skrypty basha, użyj #!/bin/bashlub jego lokalnego odpowiednika ( #!/usr/bin/env bashaby użyć pierwszego interpretera basha w PATH).
Charles Duffy,
To [[ false ]]też ma wpływ .
CMCDragonkai

Odpowiedzi:

192

Uruchamiasz polecenie [(aka test) z argumentem „false”, a nie uruchamiasz polecenie false. Ponieważ „false” nie jest pustym łańcuchem, testpolecenie zawsze się powiedzie. Aby faktycznie uruchomić polecenie, upuść [polecenie.

if false; then
   echo "True"
else
   echo "False"
fi
Chepner
źródło
4
Pomysł, że fałsz jest poleceniem, a nawet łańcuchem, wydaje mi się dziwny, ale myślę, że to działa. Dzięki.
tenmiles
31
bashnie ma typu danych Boolean, więc nie ma słów kluczowych reprezentujących prawdę i fałsz. ifOświadczenie jedynie sprawdza, czy rozkaz dasz się powiedzie lub nie. testPolecenie wykonuje wyrażenie i uda, jeśli wyrażenie jest prawdziwe; niepusty ciąg jest wyrażeniem, którego wynikiem jest prawda, tak jak w większości innych języków programowania. falseto polecenie, które zawsze zawodzi. (Analogicznie, trueto polecenie, które zawsze się udaje)
chepner
2
if [[ false ]];zwraca też prawdę. Tak jak if [[ 0 ]];. I if [[ /usr/bin/false ]];nie pomija też bloku. W jaki sposób wartość false lub 0 może być różna od zera? Cholera, to frustrujące. Jeśli to ma znaczenie, OS X 10.8; Bash 3.
jww
7
Nie pomyl falseze stałą logiczną lub 0literałem całkowitoliczbowym; oba to zwykłe struny. [[ x ]]jest równoważne [[ -n x ]]dla dowolnego ciągu x, który nie zaczyna się od łącznika. bashnie ma żadnych stałych boolowskich w żadnym kontekście. Łańcuchy, które wyglądają jak liczby całkowite, są traktowane jako takie wewnątrz (( ... ))lub z operatorami takimi jak -eqlub -ltinside [[ ... ]].
chepner
2
@ tbc0, mamy więcej problemów niż tylko to, jak coś czyta. Jeśli zmienne są ustawiane przez kod, który nie jest całkowicie pod twoją kontrolą (lub którymi można manipulować zewnętrznie, czy to przez nietypowe nazwy plików, czy w inny sposób), if $predicatemożna je łatwo wykorzystać do wykonania wrogiego kodu w sposób, który if [[ $predicate ]]nie może.
Charles Duffy,
55

Quick Boolean Primer for Bash

ifOświadczenie wykonuje polecenie jako argument (jak zrobić &&, ||itp). Całkowity kod wyniku polecenia jest interpretowany jako wartość logiczna (0 / null = prawda, 1 / else = fałsz).

testOświadczenie bierze operatorów i operandów jako argumenty i zwraca kod wynikowy w takim samym formacie jak if. Alias testinstrukcji to [, który jest często używany ifdo wykonywania bardziej złożonych porównań.

Instrukcje truei falsenic nie robią i zwracają kod wyniku (odpowiednio 0 i 1). Można ich więc używać jako literałów logicznych w Bash. Ale jeśli umieścisz instrukcje w miejscu, w którym są interpretowane jako ciągi, napotkasz problemy. W Twoim przypadku:

if [ foo ]; then ... # "if the string 'foo' is non-empty, return true"
if foo; then ...     # "if the command foo succeeds, return true"

Więc:

if [ true  ] ; then echo "This text will always appear." ; fi;
if [ false ] ; then echo "This text will always appear." ; fi;
if true      ; then echo "This text will always appear." ; fi;
if false     ; then echo "This text will never appear."  ; fi;

Jest to podobne do zrobienia czegoś podobnego echo '$foo'vs. echo "$foo".

W przypadku użycia testinstrukcji wynik zależy od użytych operatorów.

if [ "$foo" = "$bar" ]   # true if the string values of $foo and $bar are equal
if [ "$foo" -eq "$bar" ] # true if the integer values of $foo and $bar are equal
if [ -f "$foo" ]         # true if $foo is a file that exists (by path)
if [ "$foo" ]            # true if $foo evaluates to a non-empty string
if foo                   # true if foo, as a command/subroutine,
                         # evaluates to true/success (returns 0 or null)

Krótko mówiąc , jeśli po prostu chcesz przetestować coś jak pass / fail (aka „true” / „false”), a następnie przejść do polecenia iflub &&itp oświadczenie, bez nawiasów. W przypadku złożonych porównań użyj nawiasów z odpowiednimi operatorami.

I tak, zdaję sobie sprawę że nie ma czegoś takiego jak natywny typu boolean w bash, a ifi [i truesą technicznie „polecenia”, a nie „oświadczenia”; to tylko bardzo podstawowe, funkcjonalne wyjaśnienie.

Beejor
źródło
1
FYI, jeśli chcesz użyć 1/0 jako Prawda / Fałsz w swoim kodzie, to if (( $x )); then echo "Hello"; fipokazuje komunikat dla, x=1ale nie dla x=0lub x=(nieokreślony).
Jonathan H
3

Odkryłem, że mogę wykonać podstawową logikę, uruchamiając coś takiego:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C
Rodrigo
źródło
6
Jeden może , ale jest to naprawdę zły pomysł. ( $A && $B )jest zaskakująco nieefektywną operacją - tworzysz podproces poprzez wywołanie fork(), a następnie dzielisz zawartość na łańcuchy w $Acelu wygenerowania listy elementów (w tym przypadku o rozmiarze jeden) i oceniasz każdy element jako glob (w tym przypadku jeden która ocenia siebie), a następnie uruchamia wynik jako polecenie (w tym przypadku polecenie wbudowane).
Charles Duffy
3
Co więcej, jeśli twoje truei falseciągi znaków pochodzą z innego miejsca, istnieje ryzyko, że mogą faktycznie zawierać zawartość inną niż truelub false, i możesz uzyskać nieoczekiwane zachowanie, włącznie z wykonaniem dowolnego polecenia.
Charles Duffy
6
O wiele, dużo bezpieczniejsze jest pisanie A=1; B=1i if (( A && B ))- traktowanie ich jako liczb - lub [[ $A && $B ]], które traktują pusty ciąg jako fałsz, a niepusty ciąg jako prawdziwy.
Charles Duffy
3
(zwróć uwagę, że powyżej używam tylko nazw zmiennych składających się z wielkich liter, aby przestrzegać konwencji ustalonej w odpowiedzi - POSIX w rzeczywistości wyraźnie określa nazwy składające się z samych wielkich liter, które mają być używane dla zmiennych środowiskowych i elementów wbudowanych powłoki, i rezerwuje nazwy małymi literami do użytku przez aplikacje; aby uniknąć konfliktów z przyszłymi środowiskami systemu operacyjnego, szczególnie biorąc pod uwagę, że ustawienie zmiennej powłoki nadpisuje dowolną zmienną środowiskową o takiej samej nazwie, zawsze idealnie jest przestrzegać tej konwencji we własnych skryptach).
Charles Duffy,
Dzięki za wgląd!
Rodrigo,
2

Używanie prawda / fałsz usuwa trochę bałaganu w nawiasach ...

#! /bin/bash    
#  true_or_false.bash

[ "$(basename $0)" == "bash" ] && sourced=true || sourced=false

$sourced && echo "SOURCED"
$sourced || echo "CALLED"

# Just an alternate way:
! $sourced  &&  echo "CALLED " ||  echo "SOURCED"

$sourced && return || exit
psh
źródło
Uznałem to za pomocne, ale w Ubuntu 18.04 $ 0 to „-bash”, a polecenie basename zwróciło błąd. Zmiana tego testu, aby [[ "$0" =~ "bash" ]] skrypt działał dla mnie.
WiringHarness