Bash if [false]; ritorna vero


151

Ho imparato bash questa settimana e ho avuto un problema.

#!/bin/sh

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

Questo produrrà sempre True anche se la condizione sembrerebbe indicare diversamente. Se rimuovo le parentesi, []allora funziona, ma non capisco perché.


3
Ecco qualcosa per iniziare.
keyser

10
A proposito, uno script che inizia con #!/bin/shnon è uno script bash - è uno script sh POSIX. Anche se l'interprete sh POSIX sul tuo sistema è fornito da bash, disattiva un sacco di estensioni. Se vuoi scrivere script bash, usa #!/bin/basho il suo equivalente localmente appropriato ( #!/usr/bin/env bashper usare il primo interprete bash nel PERCORSO).
Charles Duffy,

Anche questo influenza [[ false ]].
CMCDragonkai,

Risposte:


192

Stai eseguendo il comando [(aka test) con l'argomento "false", non eseguendo il comando false. Poiché "false" è una stringa non vuota, il testcomando ha sempre esito positivo. Per eseguire effettivamente il comando, rilasciare il [comando.

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

4
L'idea del falso essere un comando, o anche una stringa, mi sembra strana, ma immagino che funzioni. Grazie.
tenmiles

31
bashnon ha un tipo di dati booleano e quindi nessuna parola chiave che rappresenti vero e falso. L' ifistruzione verifica semplicemente se il comando impartito ha esito positivo o negativo. Il testcomando accetta un'espressione e ha esito positivo se l'espressione è vera; una stringa non vuota è un'espressione che viene valutata come vera, proprio come nella maggior parte degli altri linguaggi di programmazione. falseè un comando che fallisce sempre. (Per analogia, trueè un comando che ha sempre successo.)
chepner,

2
if [[ false ]];ritorna anche vero. Come fa if [[ 0 ]];. E if [[ /usr/bin/false ]];non salta neanche il blocco. Come può falso o 0 valutare diverso da zero? Accidenti, questo è frustrante. Se è importante, OS X 10.8; Bash 3.
jww

7
Non confondere falsecon una costante booleana o 0con un valore intero letterale; entrambi sono solo stringhe ordinarie. [[ x ]]è equivalente a [[ -n x ]]per qualsiasi stringa xche non inizia con un trattino. bashnon ha alcun costanti booleane in qualsiasi contesto. Le stringhe che sembrano numeri interi vengono trattate come tali all'interno (( ... ))o con operatori come -eqo -ltall'interno [[ ... ]].
Chepner,

2
@ tbc0, ci sono più preoccupazioni a portata di mano rispetto al modo in cui qualcosa legge. Se le variabili sono impostate da un codice non interamente sotto il tuo controllo (o che può essere manipolato esternamente, sia da nomi di file insoliti o altro), if $predicatepuò essere facilmente abusato per eseguire codice ostile in un modo che if [[ $predicate ]]non è possibile.
Charles Duffy,

55

Un primer booleano rapido per Bash

La ifdichiarazione prende un comando come argomento (come fanno &&, ||e così via). Il codice risultato intero del comando viene interpretato come un valore booleano (0 / null = true, 1 / else = false).

L' testistruzione accetta operatori e operandi come argomenti e restituisce un codice risultato nello stesso formato di if. Un alias testdell'istruzione è [, che viene spesso utilizzato ifper eseguire confronti più complessi.

Le istruzioni truee falsenon fanno nulla e restituiscono un codice risultato (0 e 1, rispettivamente). Quindi possono essere usati come letterali booleani in Bash. Ma se metti le dichiarazioni in un posto in cui sono interpretate come stringhe, ti imbatterai in problemi. Nel tuo caso:

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

Così:

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;

Questo è simile a fare qualcosa di simile echo '$foo'contro echo "$foo".

Quando si utilizza l' testistruzione, il risultato dipende dagli operatori utilizzati.

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)

In breve , se vuoi semplicemente testare qualcosa come pass / fail (aka "true" / "false"), passa un comando alla tua istruzione ifo &&ecc., Senza parentesi. Per confronti complessi, utilizzare le parentesi con gli operatori appropriati.

E sì, sono consapevole che non esiste un tipo booleano nativo in Bash, e che ife [e e truetecnicamente "comandi" e non "istruzioni"; questa è solo una spiegazione molto basilare e funzionale.


1
Cordiali saluti, se si desidera utilizzare 1/0 come Vero / Falso nel proprio codice, questo if (( $x )); then echo "Hello"; fimostra il messaggio per x=1ma non per x=0o x=(non definito).
Jonathan H

3

Ho scoperto che posso fare alcune logiche di base eseguendo qualcosa di simile:

A=true
B=true
if ($A && $B); then
    C=true
else
    C=false
fi
echo $C

6
Si può , ma è una pessima idea. ( $A && $B )è un'operazione sorprendentemente inefficiente: stai generando un sottoprocesso tramite chiamata fork(), quindi suddivisione in stringhe del contenuto $Aper generare un elenco di elementi (in questo caso di dimensione uno) e valutazione di ogni elemento come glob (in questo caso uno che valuta se stesso), quindi eseguendo il risultato come comando (in questo caso, un comando incorporato).
Charles Duffy,

3
Inoltre, se your truee le falsestringhe provengono da qualche altra parte, c'è il rischio che possano effettivamente contenere contenuti diversi da trueo false, e si potrebbe ottenere un comportamento imprevisto fino all'esecuzione arbitraria dei comandi.
Charles Duffy,

6
È molto, molto più sicuro scrivere A=1; B=1e if (( A && B ))- trattarli come numerici - o [[ $A && $B ]], che tratta una stringa vuota come falsa e una stringa non vuota come vera.
Charles Duffy,

3
(nota che sto solo usando i nomi delle variabili maiuscole sopra per seguire le convenzioni stabilite dalla risposta - POSIX in realtà specifica esplicitamente i nomi di maiuscole da utilizzare per le variabili di ambiente e i builtin della shell e riserva i nomi minuscoli per l'uso dell'applicazione; per evitare conflitti con i futuri ambienti del sistema operativo, in particolare dato che l'impostazione di una variabile di shell sovrascrive qualsiasi variabile di ambiente con nomi simili, è sempre ideale onorare tale convenzione nei propri script).
Charles Duffy,

Grazie per gli approfondimenti!
Rodrigo,

2

L'uso di true / false rimuove un po 'di disordine tra parentesi ...

#! /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

L'ho trovato utile, ma in Ubuntu 18.04 $ 0 era "-bash" e il comando basename ha generato un errore. Modifica quel test per far funzionare [[ "$0" =~ "bash" ]] lo script per me.
WiringHarness il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.