Bash if [false]; gibt true zurück

151

Ich habe diese Woche Bash gelernt und bin auf einen Haken gestoßen.

#!/bin/sh

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

Dies gibt immer True aus, obwohl die Bedingung etwas anderes anzuzeigen scheint. Wenn ich die Klammern entferne [], funktioniert es, aber ich verstehe nicht warum.

zehn Meilen
quelle
3
Hier ist etwas, um Ihnen den Einstieg zu erleichtern.
Keyser
10
Übrigens ist ein Skript, das mit beginnt, #!/bin/shkein Bash-Skript, sondern ein POSIX-sh-Skript. Selbst wenn der POSIX sh-Interpreter auf Ihrem System von bash bereitgestellt wird, werden einige Erweiterungen deaktiviert. Wenn Sie Bash-Skripte schreiben möchten, verwenden Sie #!/bin/bashoder das lokal geeignete Äquivalent ( #!/usr/bin/env bashum den ersten Bash-Interpreter im PATH zu verwenden).
Charles Duffy
Dies betrifft [[ false ]]auch.
CMCDragonkai

Antworten:

190

Sie führen den Befehl [(aka test) mit dem Argument "false" aus und nicht den Befehl false. Da "false" eine nicht leere Zeichenfolge ist, ist der testBefehl immer erfolgreich. Löschen Sie den Befehl, um den [Befehl tatsächlich auszuführen .

if false; then
   echo "True"
else
   echo "False"
fi
chepner
quelle
4
Die Vorstellung, dass falsch ein Befehl oder sogar eine Zeichenfolge ist, scheint mir seltsam, aber ich denke, es funktioniert. Vielen Dank.
zehn Meilen
31
bashhat keinen booleschen Datentyp und daher keine Schlüsselwörter, die wahr und falsch darstellen. Die ifAnweisung prüft lediglich, ob der von Ihnen erteilte Befehl erfolgreich ist oder fehlschlägt. Der testBefehl nimmt einen Ausdruck an und ist erfolgreich, wenn der Ausdruck wahr ist. Eine nicht leere Zeichenfolge ist ein Ausdruck, der wie in den meisten anderen Programmiersprachen als wahr ausgewertet wird. falseist ein Befehl, der immer fehlschlägt. (Analog trueist ein Befehl, der immer erfolgreich ist.)
chepner
2
if [[ false ]];gibt auch true zurück. Wie auch if [[ 0 ]];. Und if [[ /usr/bin/false ]];überspringt den Block auch nicht. Wie kann false oder 0 als ungleich Null bewertet werden? Verdammt, das ist frustrierend. Wenn es darauf ankommt, OS X 10.8; Bash 3.
JWW
7
Verwechseln Sie nicht falseeine boolesche Konstante oder 0ein ganzzahliges Literal. beide sind nur gewöhnliche Saiten. [[ x ]]entspricht [[ -n x ]]einer Zeichenfolge x, die nicht mit einem Bindestrich beginnt. bashhat nicht alle Booleschen Konstanten in jedem Kontext. Zeichenfolgen, die wie Ganzzahlen aussehen, werden innerhalb (( ... ))oder mit Operatoren wie -eqoder -ltinnerhalb als solche behandelt [[ ... ]].
Chepper
2
@ tbc0, es gibt mehr Bedenken als nur, wie etwas liest. Wenn Variablen durch Code festgelegt werden, der nicht vollständig unter Ihrer Kontrolle steht (oder der extern manipuliert werden kann, sei es durch ungewöhnliche Dateinamen oder auf andere Weise), if $predicatekann dies leicht missbraucht werden, um feindlichen Code auf eine Weise auszuführen, if [[ $predicate ]]die dies nicht kann.
Charles Duffy
55

Eine schnelle boolesche Grundierung für Bash

Die ifAnweisung nimmt einen Befehl als Argument (wie auch &&, ||etc.). Der ganzzahlige Ergebniscode des Befehls wird als Boolescher Wert interpretiert (0 / null = true, 1 / else = false).

Die testAnweisung verwendet Operatoren und Operanden als Argumente und gibt einen Ergebniscode im gleichen Format wie zurück if. Ein Alias ​​der testAnweisung ist [, mit dem häufig ifkomplexere Vergleiche durchgeführt werden.

Die Anweisungen trueund falsetun nichts und geben einen Ergebniscode zurück (0 bzw. 1). So können sie in Bash als boolesche Literale verwendet werden. Wenn Sie die Anweisungen jedoch an einer Stelle platzieren, an der sie als Zeichenfolgen interpretiert werden, treten Probleme auf. In deinem Fall:

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

So:

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;

Dies ist vergleichbar mit so etwas wie tun echo '$foo'vs. echo "$foo".

Bei Verwendung der testAnweisung hängt das Ergebnis von den verwendeten Operatoren ab.

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)

Kurz gesagt , wenn Sie nur als Pass - Test etwas wollen / nicht bestanden (auch bekannt als „true“ / „false“), übergeben Sie dann einen Befehl an Ihrer ifoder &&usw. Anweisung, ohne Klammern. Verwenden Sie für komplexe Vergleiche Klammern mit den richtigen Operatoren.

Und ja, ich bin mir bewusst, dass es in Bash keinen nativen booleschen Typ gibt, und das ifund [und truesind technisch "Befehle" und keine "Anweisungen"; Dies ist nur eine sehr grundlegende, funktionale Erklärung.

Beejor
quelle
1
Zu Ihrer Information, wenn Sie 1/0 als Wahr / Falsch in Ihrem Code verwenden möchten, if (( $x )); then echo "Hello"; fiwird die Meldung für, x=1aber nicht für x=0oder x=(undefiniert) angezeigt.
Jonathan H
3

Ich habe festgestellt, dass ich eine grundlegende Logik ausführen kann, indem ich Folgendes ausführe:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C
Rodrigo
quelle
6
Man kann , aber es ist eine wirklich schlechte Idee. ( $A && $B )ist eine überraschend ineffiziente Operation - Sie erzeugen einen Unterprozess durch Aufrufen fork(), teilen dann den Inhalt von $Aauf, um eine Liste von Elementen (in diesem Fall der Größe eins) zu generieren, und bewerten jedes Element als Glob (in diesem Fall eines) das ergibt sich selbst) und führt dann das Ergebnis als Befehl aus (in diesem Fall als eingebauter Befehl).
Charles Duffy
3
Wenn Ihre trueund falseZeichenfolgen von einem anderen Ort stammen, besteht außerdem das Risiko, dass sie tatsächlich andere Inhalte als trueoder enthalten false, und Sie können unerwartetes Verhalten bis hin zur Ausführung beliebiger Befehle erhalten.
Charles Duffy
6
Es ist viel, viel sicherer zu schreiben A=1; B=1und if (( A && B ))- sie als numerisch zu behandeln - oder[[ $A && $B ]] eine leere Zeichenfolge als falsch und eine nicht leere Zeichenfolge als wahr zu behandeln.
Charles Duffy
3
(Beachten Sie, dass ich nur die oben genannten All-Caps-Variablennamen verwende, um den in der Antwort festgelegten Konventionen zu folgen. POSIX gibt explizit All-Caps-Namen an, die für Umgebungsvariablen und Shell-Builds verwendet werden sollen, und reserviert Kleinbuchstaben für die Verwendung durch Anwendungen. Um Konflikte mit zukünftigen Betriebssystemumgebungen zu vermeiden, insbesondere angesichts der Tatsache, dass das Festlegen einer Shell-Variablen jede gleichnamige Umgebungsvariable überschreibt, ist es immer ideal, diese Konvention in den eigenen Skripten zu berücksichtigen.
Charles Duffy
Danke für die Einblicke!
Rodrigo
2

Durch die Verwendung von true / false wird die Unordnung in Klammern beseitigt ...

#! /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
quelle
Ich fand das hilfreich, aber in Ubuntu 18.04 war $ 0 "-bash" und der Befehl basename warf einen Fehler aus. Ändern Sie diesen Test so, [[ "$0" =~ "bash" ]] dass das Skript für mich funktioniert.
WiringHarness