Come dichiarare e utilizzare le variabili booleane nello script di shell?

Ho provato a dichiarare una variabile booleana in uno script di shell usando la seguente syntax:

variable=$false variable=$true 

È corretto? Inoltre, se volessi aggiornare quella variabile, userei la stessa syntax? Infine, la seguente syntax per l’utilizzo di variabili booleane come espressioni corrette:

 if [ $variable ] if [ !$variable ] 

Risposta modificata (12 febbraio 2014)

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

Risposta originale

Avvertenze: 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 

Da: uso di variabili booleane in Bash

Il motivo per cui la risposta originale è inclusa qui è perché i commenti prima della revisione del 12 febbraio 2014 riguardano solo la risposta originale e molti dei commenti sono errati quando associati alla risposta rivista. Ad esempio, il commento di Dennis Williamson su bash builtin true del 2 giugno 2010 si applica solo alla risposta originale, non alla versione riveduta.

TL; DR

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

Problemi con la risposta ( originale ) di Miku

Non raccomando la risposta accettata 1 . La sua syntax è carina ma presenta alcuni difetti.

Diciamo che abbiamo la seguente condizione.

 if $var; then echo 'Muahahaha!' fi 

Nei seguenti casi 2 , questa condizione valuterà true ed eseguirà il comando nidificato.

 # Variable var not defined beforehand. Case 1 var='' # Equivalent to var="". Case 2 var= # Case 3 unset var # Case 4 var='' # Case 5 

In genere si desidera che la condizione venga valutata su true quando la variabile “booleana” var in questo esempio è impostata su true. Tutti gli altri casi sono pericolosamente fuorvianti!

L’ultimo caso (# 5) è particolarmente cattivo perché eseguirà il comando contenuto nella variabile (che è il motivo per cui la condizione restituisce true per i comandi validi 3, 4 ).

Ecco un esempio innocuo:

 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! 

Il calcolo delle variabili è più sicuro, ad es. if "$var"; then if "$var"; then . Nei casi precedenti, dovresti ricevere un avviso che il comando non è stato trovato. Ma possiamo ancora fare meglio (vedi i miei consigli in fondo).

Vedi anche la spiegazione di Mike Holt sulla risposta originale di Miku.

Problemi con la risposta di Hbar

Questo approccio ha anche un comportamento inaspettato.

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

Ci si aspetterebbe che la condizione precedente valuti su false, non eseguendo mai l’istruzione nidificata. Sorpresa!

Citando il valore ( "false" ), citando la variabile ( "$var" ), o usando test o [[ invece di [ , non fare la differenza.

Cosa raccomando:

Ecco i modi in cui ti consiglio di controllare i tuoi “booleani”. Funzionano come previsto.

 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 

Sono tutti praticamente equivalenti. Dovrai digitare qualche altra sequenza di tasti rispetto agli approcci nelle altre risposte 5, ma il tuo codice sarà più difensivo.


Le note

  1. La risposta di Miku è stata modificata e non contiene più (noti) difetti.
  2. Non una lista esaustiva.
  3. Un comando valido in questo contesto indica un comando che esiste. Non importa se il comando è usato correttamente o in modo errato. Ad esempio, l’ man woman sarebbe ancora considerato un comando valido, anche se non esiste una pagina di questo tipo.
  4. Per comandi non validi (inesistenti), Bash si lamenterà semplicemente che il comando non è stato trovato.
  5. Se ti interessa la lunghezza, la prima raccomandazione è la più breve.

Sembra che ci sia qualche malinteso riguardo al built-in di Bash true e, più specificamente, a come Bash si espande e interpreta le espressioni tra parentesi.

Il codice nella risposta di miku non ha assolutamente nulla a che fare con il builtin di Bash true , né /bin/true , né alcun altro sapore del true comando. In questo caso, true non è nient’altro che una semplice stringa di caratteri, e non viene mai effettuata alcuna chiamata al true comando / builtin, né dall’assegnazione di variabili, né dalla valutazione dell’espressione condizionale.

Il seguente codice è funzionalmente identico al codice nella risposta di miku:

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

L’ unica differenza è che i quattro caratteri confrontati sono “y”, “e”, “a” e “h” invece di “t”, “r”, “u” e “e”. Questo è tutto. Non è stato fatto alcun tentativo di chiamare un comando o un built-in chiamato yeah , né esiste (nell’esempio di miku) alcun tipo di gestione speciale in corso quando Bash analizza il token true . È solo una stringa, e completamente arbitraria.

Aggiornamento (2014-02-19): Dopo aver seguito il collegamento nella risposta di miku, ora vedo da dove proviene parte della confusione. La risposta di Miku usa parentesi singole, ma lo snippet di codice a cui si collega non usa parentesi. È appena:

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

Entrambi i frammenti di codice si comportano allo stesso modo, ma le parentesi cambiano completamente ciò che sta succedendo sotto il cofano.

Ecco cosa sta facendo Bash in ciascun caso:

Nessuna parentesi:

  1. Espandi la variabile $the_world_is_flat nella stringa "true" .
  2. Tentativo di analizzare la stringa "true" come comando.
  3. Trova ed esegui il true comando (un builtin o /bin/true , a seconda della versione di Bash).
  4. Confronta il codice di uscita del comando true (che è sempre 0) con 0. Ricorda che nella maggior parte delle shell, un codice di uscita di 0 indica successo e qualsiasi altra cosa indica un errore.
  5. Poiché il codice di uscita era 0 (esito positivo), eseguire la clausola then dell’istruzione if

Parentesi:

  1. Espandi la variabile $the_world_is_flat nella stringa "true" .
  2. Analizza l’espressione condizionale ora completamente espansa, che è del formato string1 = string2 . L’operatore = è l’operatore di confronto delle stringhe di bash. Così…
  3. Fai un confronto tra stringhe su "true" e "true" .
  4. Sì, le due stringhe erano uguali, quindi il valore del condizionale è vero.
  5. Esegui la clausola then dell’istruzione if .

Il codice senza parentesi funziona perché il comando true restituisce un codice di uscita 0, che indica il successo. Il codice tra parentesi funziona, perché il valore di $the_world_is_flat è identico alla stringa letterale true sul lato destro del = .

Per guidare il punto a casa, considera i seguenti due snippet di codice:

Questo codice (se eseguito con i privilegi di root) riavvierà il tuo computer:

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

Questo codice stampa semplicemente “Bel tentativo”. Il comando di riavvio non viene chiamato.

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

Aggiornamento (2014-04-14) Per rispondere alla domanda nei commenti riguardanti la differenza tra = e == : AFAIK, non c’è differenza. L’operatore == è un sinonimo specifico di Bash per = , e per quanto ho visto, funzionano esattamente allo stesso modo in tutti i contesti.

Si noti, tuttavia, che sto specificatamente parlando degli operatori di confronto delle stringhe = e == usati nei test [ ] o [[ ]] . Non sto suggerendo che = e == siano intercambiabili ovunque in bash.

Ad esempio, ovviamente non puoi eseguire l’assegnazione di variabili con == , come var=="foo" (beh tecnicamente puoi farlo, ma il valore di var sarà "=foo" , perché Bash non sta vedendo un == operatore qui, sta vedendo un operatore = (assegnazione), seguito dal valore letterale ="foo" , che diventa "=foo" ).

Inoltre, anche se = e == sono intercambiabili, è necessario tenere presente che il modo in cui funzionano questi test dipende dal fatto che lo si utilizzi all’interno [ ] o [[ ]] , e anche sul fatto che gli operandi siano quotati o meno. Puoi leggere ulteriori informazioni a riguardo in Advanced Bash Scripting Guide: 7.3 Altri operatori di confronto (scorri fino alla discussione di = e == ).

Utilizzare le espressioni aritmetiche.

 #!/bin/bash false=0 true=1 ((false)) && echo false ((true)) && echo true ((!false)) && echo not false ((!true)) && echo not true 

Produzione:

vero
non falso

Molto tempo fa, quando tutto ciò che avevamo era sh , i booleani venivano gestiti facendo affidamento su una convenzione del programma di test in cui il test restituisce uno stato di uscita falso se eseguito senza argomenti. Ciò consente di pensare a una variabile non impostata come falsa e variabile impostata su qualsiasi valore come vera. Oggi, il test è incorporato in bash ed è comunemente noto con il suo alias di un carattere [ (o un eseguibile da usare nelle shell prive di esso, come nota dolmen):

 FLAG="up or " 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 

A causa della citazione delle convenzioni, gli scrittori di script preferiscono utilizzare il comando composto [[ che simula il test ma ha una syntax migliore: le variabili con spazi non hanno bisogno di essere citate, si può usare && e || come operatori logici con strana precedenza e non ci sono limitazioni POSIX sul numero di termini.

Ad esempio, per determinare se FLAG è impostato e COUNT è un numero maggiore di 1:

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

Questa roba può creare confusione quando sono necessari spazi, stringhe di lunghezza zero e variabili nulle e anche quando lo script deve lavorare con diverse shell.

Come dichiarare e utilizzare le variabili booleane nello script di shell?

A differenza di molti altri linguaggi di programmazione, Bash non segna le sue variabili per “tipo”. [1]

Quindi la risposta è abbastanza chiara. Non ci sono boolean variable in bash. Però :

Usando una dichiarazione dichiarativa, possiamo limitare l’assegnazione del valore alle variabili. [2]

 #!/bin/bash declare -ir BOOL=(0 1) #remember BOOL can't be unset till this shell terminate 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" 

L’opzione r in declare e readonly viene utilizzata per dichiarare esplicitamente che le variabili sono di sola lettura . Spero che lo scopo sia chiaro.

Per farla breve:

Non ci sono booleani in bash

Quello che bash ha, è espressioni booleane in termini di confronto e condizioni. Detto questo, ciò che puoi dichiarare e confrontare in bash sono stringhe e numeri. Questo è tutto.

Ovunque vedi true o false in bash, è una stringa o un comando / builtin che viene utilizzato solo per il suo codice di uscita.

Questa syntax …

 if true; then ... 

è essenzialmente …

 if COMMAND; then ... 

La condizione è vera ogni volta che il comando restituisce il codice di uscita 0.

Potresti anche fare questo:

 if which foo; then echo "program foo found"; fi 

Quando si usano le parentesi quadre o il comando test , si fa affidamento sul codice di uscita di quel costrutto. Tieni presente che [ ] e [[ ]] sono anche solo comandi / builtin come qualsiasi altro. Così …

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

è solo zucchero sintattico per …

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

Quindi, quando si utilizza true e false in uno dei costrutti summenzionati, si passa effettivamente solo la stringa "true" o "false" al comando testing. Ecco un esempio:

Che ci crediate o meno, ma quelle condizioni stanno dando lo stesso risultato :

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

TL; DR; confrontare sempre contro stringhe o numeri

Per renderlo chiaro ai futuri lettori, consiglierei sempre di usare le virgolette su true e false :

FARE

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

NON

 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 `==` 

Può essere

 if [[ "${var}" != "true" ]]; then ... # creates impression of booleans. Can be used for strict checking of dangerous operations. This condition is false for anything but the literal string "true". 

Invece di fingere un booleano e lasciare una trappola ai futuri lettori, perché non usare solo un valore migliore di vero e falso?

Per esempio:

 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 

POSIX (interfaccia per sistema operativo portatile)

Mi manca qui il punto chiave, che è la portabilità. Ecco perché il mio header ha POSIX in sé.

In sostanza, tutte le risposte votate sono corrette, con l’eccezione che sono troppo specifiche per BASH .

In pratica, desidero solo aggiungere ulteriori informazioni sulla portabilità.


  1. [ e ] parentesi come in [ "$var" = true ] non sono necessarie, e puoi ometterle e usare direttamente il comando test :

     test "$var" = true && CodeIfTrue || CodeIfFalse 
  2. Immagina cosa significano queste parole true e false per la shell, mettiti alla prova tu stesso:

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

    Ma usando le virgolette:

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

    Lo stesso vale per:

     echo $(("false")) 

    La shell non può interpretarlo a parte una stringa. Spero che tu stia prendendo l’idea di quanto sia buono usare parole chiave appropriate senza virgolette.

    Ma nessuno lo ha detto nelle risposte precedenti.

  3. Cosa significa? Bene, molte cose.

    • Dovresti abituarti alle parole chiave booleane che vengono effettivamente trattate come numeri, che è true = 0 e false = 1 , ricorda che tutti i valori diversi da zero vengono trattati come false .

    • Dato che sono trattati come numeri, dovresti trattarli così, cioè se definisci la variabile dì:

       var_a=true echo "$var_a" 
        true 

      puoi crearne un valore opposto con:

       var_a=$((1 - $var_a)) echo "$var_a" 
       1 

      Come puoi vedere tu stesso, la shell stampa la true stringa per la prima volta che la usi, ma da allora funziona tutto tramite il numero 0 o 1 , rispettivamente.


Infine, cosa dovresti fare con tutte quelle informazioni

  • Prima buona abitudine sarebbe assegnare 0 anziché true ; 1 invece di false .

  • La seconda buona abitudine sarebbe quella di verificare se la variabile è / non è uguale a zero:

     test "$var" -eq 0 && CodeIfTrue || CodeIfFalse 

Bill Parker viene votato in ribasso perché le sue definizioni sono invertite dalla normale convenzione di codice. Normalmente, true è definito come 0 e false è definito come diverso da zero. 1 funzionerà per falso, così come 9999 e -1. Lo stesso vale per i valori di ritorno della funzione – 0 è successo e qualsiasi valore diverso da zero è negativo. Spiacente, non ho ancora la credibilità della strada per votare o per rispondere direttamente a lui.

Bash raccomanda di utilizzare parentesi doppie ora come un’abitudine invece di parentesi singole, e il collegamento che Mike Holt ha dato spiega le differenze nel modo in cui funzionano. 7.3. Altri operatori di confronto

Per prima cosa, -eq è un operatore numerico, quindi avere il codice

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

emetterà una dichiarazione di errore, in attesa di un’espressione intera. Questo vale per entrambi i parametri, poiché nessuno dei due è un valore intero. Tuttavia, se mettiamo due parentesi quadre, non pubblicherà una dichiarazione di errore, ma produrrà un valore errato (beh, nel 50% delle possibili permutazioni). Valuterà su [[0 -eq true]] = successo, ma anche su [[0 -eq false]] = successo, che è sbagliato (hmmm …. che dire di quel builtin che è un valore numerico?).

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

Ci sono altre permutazioni del condizionale che forniranno anche output errati. Fondamentalmente, qualsiasi cosa (diversa dalla condizione di errore sopra elencata) imposta una variabile su un valore numerico e la confronta con un builtin vero / falso, o imposta una variabile su un builtin vero / falso e la confronta con un valore numerico. Inoltre, tutto ciò che imposta una variabile su un builtin vero / falso e fa un confronto usando -eq . Quindi evitare -eq per i confronti booleani ed evitare l’uso di valori numerici per i confronti booleani. Ecco un riepilogo delle permutazioni che darà risultati non validi:

 #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 

Quindi, ora a ciò che funziona. Usa builtin vero / falso sia per il tuo confronto che per le tue valutazioni (come ha notato Mike Hunt, non includerle tra virgolette). Quindi utilizzare il segno di uguale o singolo o doppio (= o ==) e le parentesi singola o doppia ([] o [[]]). Personalmente, mi piace il doppio segno di uguale, perché mi ricorda i confronti logici in altri linguaggi di programmazione e le doppie virgolette solo perché mi piace digitare. Quindi questi funzionano:

 #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 

Ecco qua.

Ecco una implementazione di una mano breve 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 } 

Esempio 1

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

Esempio 2

 my_boolean=false ! _if ${my_boolean} && echo "Not True is True" 

Ecco un semplice esempio che funziona per me:

 temp1=true temp2=false if [ "$temp1" = true ] || [ "$temp2" = true ] then echo "Do something." else echo "Do something else." fi 

Ho trovato le risposte esistenti confuse.

Personalmente, voglio solo avere qualcosa che sembri e funzioni come C.

Questo frammento funziona più volte al giorno in produzione:

 snapshotEvents=true if ($snapshotEvents) then # do stuff if true fi 

e per far contenti tutti, ho provato:

 snapshotEvents=false if !($snapshotEvents) then # do stuff if false fi 

Che ha funzionato anche bene.

$snapshotEvents valuta il contenuto del valore della variabile. Quindi hai bisogno di $ .

Non hai davvero bisogno delle parentesi, le trovo solo utili.

  • Testato su: GNU Bash, versione 4.1.11 (2) -release

  • Bash Guide for Beginners , Machtelt Garrels, v1.11, 2008

Ecco un miglioramento della risposta originale di miku, che affronta le preoccupazioni di Dennis Williamson sul caso, in cui la variabile non è impostata:

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

E per verificare se la variabile è false :

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

Su altri casi con un contenuto sgradevole nella variabile, questo è un problema con qualsiasi input esterno inviato a un programma.

Qualsiasi input esterno deve essere convalidato prima di fidarsi di esso. Ma quella validazione deve essere fatta solo una volta, quando viene ricevuto quell’input.

Non deve influire sulle prestazioni del programma eseguendolo su ogni utilizzo della variabile, come suggerisce Dennis Williamson .

Usa il senso comune. In molte lingue, 1 è vero e 0 è falso. Fortunatamente questo vale anche per Bash.

Ingresso:

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

Produzione:

 true false 

Fonte :

((espressione))

L’espressione viene valutata in base alle regole descritte di seguito in VALUTAZIONE ARITMETICA. Se il valore dell’espressione è diverso da zero, lo stato di ritorno è 0; altrimenti lo stato di ritorno è 1. Questo è esattamente equivalente a lasciare “espressione”.

Bash confonde davvero il problema con parole del calibro di [ , [[ , (( , $(( , ecc.

Tutti trattano gli spazi dei codici degli altri. Immagino che sia per lo più storico, dove Bash doveva fingere di essere occasionalmente.

Il più delle volte, posso semplicemente scegliere un metodo e attenerci ad esso. In questo caso, tendo a dichiarare (preferibilmente in un file di libreria comune che posso includere con . Nei miei script effettivi).

 TRUE=1; FALSE=0 

Posso quindi usare l’operatore aritmetico (()) per testare così.

 testvar=$FALSE if [[ -d ${does_directory_exist} ]] then testvar=$TRUE; fi if (( testvar == TRUE )); then # do stuff because the directory does exist fi 
  1. Devi essere disciplinato. Il tuo testvar deve essere impostato su $TRUE o $FALSE in ogni momento.

  2. In (()) comparatori, non è necessario il precedente $ , che lo rende più leggibile.

  3. Posso usare (()) perché $TRUE=1 e $FALSE=0 , cioè valori numerici.

  4. Il rovescio della medaglia è dover usare un $ occasionalmente:

     testvar=$TRUE 

    che non è così carino

Non è una soluzione perfetta, ma copre ogni caso, ho bisogno di un test del genere.