Restituisce un valore booleano da una funzione Bash


211

Voglio scrivere una funzione bash che controlla se un file ha determinate proprietà e restituisce vero o falso. Quindi posso usarlo nei miei script nel "if". Ma cosa dovrei tornare?

function myfun(){ ... return 0; else return 1; fi;}

quindi lo uso in questo modo:

if myfun filename.txt; then ...

ovviamente questo non funziona. Come può essere realizzato?


3
rilascia la functionparola chiave, myfun() {...}basta
Glenn Jackman,

2
Ciò che conta ifè lo stato di uscita zero di myfun: se myfunesce con 0, then ...viene eseguito; se è qualcos'altro else ... viene eseguito.
Eelvex,

7
@nhed: la functionparola chiave è bashism e causerà errori di sintassi in alcune altre shell. Fondamentalmente, è inutile o proibito, quindi perché usarlo? Non è nemmeno utile come obiettivo grep, dal momento che potrebbe non essere lì (grep ()invece).
Gordon Davisson,

3
@GordonDavisson: che cosa? ci sono altre conchiglie? ;-)
nhed

Si prega di non utilizzare 0 e 1. Vedere stackoverflow.com/a/43840545/117471
Bruno Bronosky

Risposte:


333

Usa 0 per vero e 1 per falso.

Campione:

#!/bin/bash

isdirectory() {
  if [ -d "$1" ]
  then
    # 0 = true
    return 0 
  else
    # 1 = false
    return 1
  fi
}


if isdirectory $1; then echo "is directory"; else echo "nopes"; fi

modificare

Dal commento di @amichair, anche questi sono possibili

isdirectory() {
  if [ -d "$1" ]
  then
    true
  else
    false
  fi
}


isdirectory() {
  [ -d "$1" ]
}

4
No, non è necessario farlo - guarda l'esempio.
Erik

46
Per una migliore leggibilità puoi usare il comando 'true' (che non fa nulla e si completa correttamente, cioè restituisce 0) e il comando 'false' (che non fa nulla e si completa senza successo, cioè restituisce un valore diverso da zero). Inoltre, una funzione che termina senza un'istruzione di ritorno esplicita restituisce il codice di uscita dell'ultimo comando eseguito, quindi nell'esempio sopra, il corpo della funzione può essere ridotto a solo [ -d "$1" ].
amichair

24
Bengt: ha senso quando lo pensi come "codice di errore": codice di errore 0 = tutto è andato bene = 0 errori; codice di errore 1 = la cosa principale che questa chiamata avrebbe dovuto fallire; altro: fallire! cercalo nella manpage.
pecora volante

7
Ha senso se si considera che nella programmazione le cose di solito possono avere successo solo in un modo, ma possono fallire in infiniti modi. Beh, forse non infinito, ma un sacco, le probabilità sono impilate contro di noi. Success / Error (s) non è booleano. Penso che questo "Usa 0 per vero e 1 per falso." dovrebbe leggere "Usa 0 per esito positivo e diverso da zero per errore".
Davos,

6
Si prega di non utilizzare 0 e 1. Vedere stackoverflow.com/a/43840545/117471
Bruno Bronosky

167

Perché dovresti preoccuparti di quello che dico nonostante ci sia una risposta di oltre 250 voti positivi

Non è quello 0 = truee 1 = false. È: zero significa nessun fallimento (successo) e diverso da zero significa fallimento (di tipo N) .

Mentre la risposta selezionata è tecnicamente "vera" , non inserire return 1** nel codice per falso . Avrà diversi effetti collaterali sfortunati.

  1. Gli sviluppatori esperti ti vedranno come un dilettante (per il motivo di seguito).
  2. Gli sviluppatori esperti non lo fanno (per tutti i motivi di seguito).
  3. È soggetto a errori.
    • Anche gli sviluppatori esperti possono scambiare 0 e 1 rispettivamente come falso e vero (per il motivo sopra).
  4. Richiede (o incoraggerà) commenti estranei e ridicoli.
  5. In realtà è meno utile degli stati di restituzione impliciti.

Impara un po 'di bash

Il manuale di bash dice (enfasi sul mio)

return [n]

Provoca l'interruzione dell'esecuzione di una funzione shell e restituisce il valore n al suo chiamante. Se n non viene fornito , il valore restituito è lo stato di uscita dell'ultimo comando eseguito nella funzione.

Pertanto, non dobbiamo MAI usare 0 e 1 per indicare Vero e Falso. Il fatto che lo facciano è essenzialmente una conoscenza banale utile solo per il debug del codice, le domande per le interviste e per far esplodere le menti dei neofiti.

Il manuale di Bash dice anche

altrimenti lo stato di ritorno della funzione è lo stato di uscita dell'ultimo comando eseguito

Il manuale di Bash dice anche

( $? ) Si espande allo stato di uscita dell'ultima pipeline in primo piano eseguita .

Whoa, aspetta. Tubatura? Passiamo al manuale di bash ancora una volta.

Una pipeline è una sequenza di uno o più comandi separati da uno degli operatori di controllo '|' o '| &'.

Sì. Hanno detto che 1 comando è una pipeline. Pertanto, tutte e 3 queste citazioni dicono la stessa cosa.

  • $? ti dice cosa è successo per ultimo.
  • Bolle.

La mia risposta

Quindi, mentre @Kambus ha dimostrato che con una funzione così semplice, non returnè necessario affatto. Penso che sia stato irrealisticamente semplice rispetto alle esigenze della maggior parte delle persone che leggeranno questo.

Perché return?

Se una funzione restituisce lo stato di uscita dell'ultimo comando, perché utilizzarla return? Perché provoca l'esecuzione di una funzione.

Interrompere l'esecuzione in più condizioni

01  function i_should(){
02      uname="$(uname -a)"
03
04      [[ "$uname" =~ Darwin ]] && return
05
06      if [[ "$uname" =~ Ubuntu ]]; then
07          release="$(lsb_release -a)"
08          [[ "$release" =~ LTS ]]
09          return
10      fi
11
12      false
13  }
14
15  function do_it(){
16      echo "Hello, old friend."
17  }
18
19  if i_should; then
20    do_it
21  fi

Quello che abbiamo qui è ...

La linea 04è un ritorno esplicito [-ish] vero perché l'RHS di &&viene eseguito solo se l'LHS era vero

La linea 09restituisce vero o falso corrispondente allo stato della linea08

La riga 13restituisce false a causa della riga12

(Sì, questo può essere risolto, ma l'intero esempio è inventato.)

Un altro modello comune

# Instead of doing this...
some_command
if [[ $? -eq 1 ]]; then
    echo "some_command failed"
fi

# Do this...
some_command
status=$?
if ! $(exit $status); then
    echo "some_command failed"
fi

Notare come l'impostazione di una statusvariabile demistifica il significato di $?. (Ovviamente sai cosa $?significa, ma qualcuno meno esperto di quello che dovresti fare a Google un giorno. A meno che il tuo codice non stia facendo trading ad alta frequenza, mostra un po 'di amore , imposta la variabile.) Ma il vero take-away è che "se non esiste lo stato "o viceversa" se lo stato di uscita "può essere letto ad alta voce e spiegare il loro significato. Tuttavia, l' ultimo potrebbe essere un po 'troppo ambizioso perché vedere la parola exitpotrebbe farti pensare che stia uscendo dallo script, quando in realtà sta uscendo dalla $(...)subshell.


** Se insisti assolutamente sull'uso return 1di false, ti suggerisco almeno di usarlo return 255invece. Ciò causerà il tuo sé futuro o qualsiasi altro sviluppatore che deve mantenere il codice per mettere in dubbio "perché è 255?" Quindi almeno presteranno attenzione e avranno maggiori possibilità di evitare un errore.


1
@ZeroPhase 1 & 0 per falso e vero sarebbe ridicolo. Se si dispone di un tipo di dati binario, non vi è alcun motivo per questo. Quello che hai a che fare con bash è un codice di stato che riflette il successo (singolare) e i fallimenti (plurale). È " ifsuccesso, elsefallo , fallo". Successo in cosa? Potrebbe controllare vero / falso, potrebbe essere alla ricerca di una stringa, numero intero, file, directory, permessi di scrittura, glob, regex, grep o qualsiasi altro comando soggetto a errori .
Bruno Bronosky,

3
Evitare l'uso standard di 0/1 come valore di ritorno solo perché è ottuso e soggetto a confusione è sciocco. L'intero linguaggio della shell è ottuso e soggetto a confusione. Bash stesso utilizza la convenzione 0/1 = true / false nei propri comandi truee false. Cioè, la parola chiave viene trueletteralmente valutata con un codice di stato 0. Inoltre, le dichiarazioni if-then per natura operano su valori booleani, non su codici di successo. Se si verifica un errore durante un'operazione booleana, non dovrebbe restituire vero o falso ma interrompere semplicemente l'esecuzione. Altrimenti si ottengono falsi positivi (gioco di parole).
Beejor,

1
"if! $ (exit $ status); then" - Questo merita una spiegazione migliore. Non è intuitivo Devo pensare se il programma uscirà prima di stampare il messaggio o no. Il resto della tua risposta è stata buona, ma questo l'ha rovinato.
Craig Hicks,

1
@BrunoBronosky Ho letto la tua risposta e penso di comprendere la distinzione tra la pipeline di contenuti (stdin-> stdout) e i codici di errore nei valori restituiti. Tuttavia, non capisco perché l'uso return 1debba essere evitato. Supponendo di avere una validatefunzione trovo ragionevole return 1se la validazione fallisce. Dopotutto, questo è un motivo per interrompere l'esecuzione di uno script se non è gestito correttamente (ad esempio, durante l'utilizzo set -e).
JepZ,

1
@Jepz è un'ottima domanda. Hai ragione, return 1è valido. La mia preoccupazione sono tutti i commenti qui che dicono "0 = vero 1 = falso è [inserire parola negativa]". Quelle persone probabilmente leggeranno il tuo codice un giorno. Quindi per loro vedere return 255(o 42 o anche 2) dovrebbe aiutarli a pensarci di più e non confonderlo con "vero". set -elo prenderò ancora.
Bruno Bronosky,

31
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes

1
Che dire della negazione?
einpoklum,

Che ne pensi, @einpoklum?
Mark Reed,

@MarkReed: intendevo aggiungere il caso "else" al tuo esempio.
einpoklum,

"percorso" myfun || echo no
Hrobky

13

Fai attenzione quando controlli la directory solo con l'opzione -d!
se la variabile $ 1 è vuota, il controllo avrà comunque esito positivo. A dire il vero, controlla anche che la variabile non sia vuota.

#! /bin/bash

is_directory(){

    if [[ -d $1 ]] && [[ -n $1 ]] ; then
        return 0
    else
        return 1
    fi

}


#Test
if is_directory $1 ; then
    echo "Directory exist"
else
    echo "Directory does not exist!" 
fi

1
Non sono sicuro di come questo risponda alla domanda posta. Mentre è bello sapere che un $ 1 vuoto può restituire un vero quando è vuoto, non fornisce alcuna idea su come restituire vero o falso da una funzione bash. Suggerirei di creare una nuova domanda "Cosa succede quando si esegue un test su una variabile shell vuota?" E poi pubblicando questo come risposta.
DRaehal,

3
Si noti che se si aggiungono le virgolette appropriate a $1( "$1"), non è necessario verificare la presenza di una variabile vuota. La [[ -d "$1" ]]fallirebbe perché questa ""non è una directory.
Morgents

3

Ho incontrato un punto (non ancora esplicitamente menzionato?) Su cui mi sono imbattuto. Cioè, non come restituire il valore booleano, ma piuttosto come valutarlo correttamente!

Stavo cercando di dire if [ myfunc ]; then ..., ma è semplicemente sbagliato. Non devi usare le parentesi! if myfunc; then ...è il modo di farlo.

Come su @Bruno e altri ribaditi, truee falsesono comandi , non valori! Questo è molto importante per comprendere i booleani negli script di shell.

In questo post, ho spiegato e dimostrato usando variabili booleane : https://stackoverflow.com/a/55174008/3220983 . Consiglio vivamente di verificarlo, perché è così strettamente correlato.

Qui, fornirò alcuni esempi di restituzione e valutazione di booleani dalle funzioni:

Questo:

test(){ false; }                                               
if test; then echo "it is"; fi                                 

Non produce alcun output di eco. (ovvero false restituisce false)

test(){ true; }                                                
if test; then echo "it is"; fi                                 

produce:

it is                                                        

(ovvero true restituisce vero)

E

test(){ x=1; }                                                
if test; then echo "it is"; fi                                 

produce:

it is                                                                           

Perché 0 (ovvero vero) è stato restituito implicitamente .

Ora, questo è ciò che mi stava rovinando ...

test(){ true; }                                                
if [ test ]; then echo "it is"; fi                             

produce:

it is                                                                           

E

test(){ false; }                                                
if [ test ]; then echo "it is"; fi                             

ANCHE produce:

it is                                                                           

L'uso delle parentesi qui ha prodotto un falso positivo ! (Ne deduco che il risultato del comando "esterno" è 0.)

Il punto più importante del mio post è: non usare le parentesi per valutare una funzione booleana (o variabile) come faresti per un tipico controllo di uguaglianza, ad esempio if [ x -eq 1 ]; then...!


2

Utilizzare i comandi trueo falseimmediatamente prima di return, quindi returnsenza parametri. Il returnutilizzerà automaticamente il valore del tuo ultimo comando.

Fornire argomenti returnincoerenti, digitare specifici e soggetti a errori se non si utilizza 1 o 0. E come indicato in precedenza, utilizzare 1 o 0 qui non è il modo giusto di avvicinarsi a questa funzione.

#!/bin/bash

function test_for_cat {
    if [ $1 = "cat" ];
    then
        true
        return
    else
        false
        return
    fi
}

for i in cat hat;
do
    echo "${i}:"
    if test_for_cat "${i}";
    then
        echo "- True"
    else
        echo "- False"
    fi
done

Produzione:

$ bash bash_return.sh

cat:
- True
hat:
- False

1

Potrebbe funzionare se riscrivi questo function myfun(){ ... return 0; else return 1; fi;}come questo function myfun(){ ... return; else false; fi;}. Cioè se falseè l'ultima istruzione nella funzione si ottiene un risultato falso per l'intera funzione ma si returninterrompe comunque la funzione con il risultato vero. Credo che sia vero almeno per il mio interprete bash.


1

Ho trovato la forma più breve per testare l'output della funzione è semplicemente

do_something() {
    [[ -e $1 ]] # e.g. test file exists
}

do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }

1

Per motivi di leggibilità del codice, credo che la restituzione di true / false dovrebbe:

  • essere su una riga
  • essere un comando
  • sii facile da ricordare
  • menziona la parola chiave returnseguita da un'altra parola chiave ( trueo false)

La mia soluzione è return $(true)o return $(false)come mostrato:

is_directory()
{
    if [ -d "${1}" ]; then
        return $(true)
    else
        return $(false)
    fi
}

0

In seguito a @Bruno Bronosky e @mrteatime, offro il suggerimento di scrivere semplicemente il tuo ritorno booleano "indietro". Questo è ciò che intendo:

foo()
{
    if [ "$1" == "bar" ]; then
        true; return
    else
        false; return
    fi;
}

Ciò elimina i brutti requisiti di due righe per ogni dichiarazione di reso.

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.