Retourner un booléen à partir d'une fonction Bash

211

Je veux écrire une fonction bash qui vérifie si un fichier a certaines propriétés et renvoie vrai ou faux. Ensuite, je peux l'utiliser dans mes scripts dans le "si". Mais que dois-je retourner?

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

alors je l'utilise comme ceci:

if myfun filename.txt; then ...

bien sûr, cela ne fonctionne pas. Comment cela peut-il être accompli?

luca
la source
3
laissez tomber le functionmot clé, myfun() {...}suffit
glenn jackman
2
Ce qui compte, ifc'est le statut de sortie zéro myfun: si vous quittez myfunavec 0, then ...est exécuté; si c'est autre chose else ... est exécuté.
Eelvex
7
@nhed: le functionmot-clé est un bashisme, et provoquera des erreurs de syntaxe dans certains autres shells. Fondamentalement, c'est soit inutile, soit interdit, alors pourquoi l'utiliser? Ce n'est même pas utile comme cible de grep, car il pourrait ne pas être là (grep for à la ()place).
Gordon Davisson
3
@GordonDavisson: quoi? il y a d'autres coquilles? ;-)
nhed
Veuillez ne pas utiliser 0 et 1. Voir stackoverflow.com/a/43840545/117471
Bruno Bronosky

Réponses:

333

Utilisez 0 pour vrai et 1 pour faux.

Échantillon:

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

Éditer

D'après le commentaire de @ amichair, ceux-ci sont également possibles

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


isdirectory() {
  [ -d "$1" ]
}
Erik
la source
4
Non, vous n'avez pas besoin de le faire - voir l'exemple.
Erik
46
Pour une meilleure lisibilité, vous pouvez utiliser la commande «true» (qui ne fait rien et se termine avec succès, c'est-à-dire retourne 0) et la commande «false» (qui ne fait rien et se termine sans succès, c'est-à-dire qui renvoie une valeur non nulle). En outre, une fonction qui se termine sans instruction de retour explicite renvoie le code de sortie de la dernière commande exécutée, donc dans l'exemple ci-dessus, le corps de la fonction peut être réduit à seulement [ -d "$1" ].
amichair
24
Bengt: cela a du sens lorsque vous le considérez comme un «code d'erreur»: code d'erreur 0 = tout s'est bien passé = 0 erreur; code d'erreur 1 = la principale chose que cet appel était censé faire a échoué; sinon: échec! recherchez-le dans la page de manuel.
vol de moutons
7
Cela a du sens lorsque vous considérez qu'en programmation, les choses ne peuvent généralement réussir que dans un sens, mais peuvent échouer de manière infinie. Eh bien peut-être pas infini, mais beaucoup, les chances sont contre nous. Les succès / erreurs ne sont pas booléens. Je pense que cela "Utilisez 0 pour vrai et 1 pour faux." devrait indiquer "Utiliser 0 pour réussir et non nul pour échouer".
Davos
6
Veuillez ne pas utiliser 0 et 1. Voir stackoverflow.com/a/43840545/117471
Bruno Bronosky
167

Pourquoi vous devriez vous soucier de ce que je dis malgré qu'il y ait une réponse de plus de 250 votes positifs

Ce n'est pas ça 0 = trueet 1 = false. Il est: zéro signifie pas d'échec (succès) et non nul signifie échec (de type N) .

Bien que la réponse sélectionnée soit techniquement "vraie", veuillez ne pas mettre return 1** dans votre code pour faux . Il aura plusieurs effets secondaires malheureux.

  1. Les développeurs expérimentés vous repéreront comme un amateur (pour la raison ci-dessous).
  2. Les développeurs expérimentés ne le font pas (pour toutes les raisons ci-dessous).
  3. Il est sujet aux erreurs.
    • Même les développeurs expérimentés peuvent confondre 0 et 1 comme faux et vrai respectivement (pour la raison ci-dessus).
  4. Cela nécessite (ou encouragera) des commentaires étrangers et ridicules.
  5. C'est en fait moins utile que les statuts de retour implicites.

Apprenez quelques bash

Le manuel bash dit (soulignement le mien)

retourner [n]

Faire arrêter une fonction shell et renvoyer la valeur n à son appelant. Si n n'est pas fourni , la valeur de retour est l' état de sortie de la dernière commande exécutée dans la fonction.

Par conséquent, nous ne devons JAMAIS utiliser 0 et 1 pour indiquer Vrai et Faux. Le fait qu'ils le fassent est essentiellement une connaissance insignifiante utile uniquement pour le débogage du code, les questions d'entrevue et la stupéfaction des débutants.

Le manuel bash dit également

sinon l'état de retour de la fonction est l' état de sortie de la dernière commande exécutée

Le manuel bash dit également

( $? ) Se développe jusqu'à l' état de sortie du dernier pipeline de premier plan exécuté .

Attends. Pipeline? Passons au manuel bash une fois de plus.

Un pipeline est une séquence d' une ou plusieurs commandes séparées par l'un des opérateurs de contrôle '|' ou '| &'.

Oui. Ils ont dit que 1 commandement est un pipeline. Par conséquent, les 3 de ces citations disent la même chose.

  • $? vous dit ce qui s'est passé en dernier.
  • Ça bouillonne.

Ma réponse

Ainsi, alors que @Kambus a démontré qu'avec une fonction aussi simple, aucune returnn'est nécessaire du tout. Je pense que c'était irréaliste par rapport aux besoins de la plupart des gens qui liront ceci.

Pourquoi return?

Si une fonction doit retourner l'état de sortie de sa dernière commande, pourquoi l'utiliser return? Parce que cela provoque l'arrêt d'une fonction.

Arrêter l'exécution dans plusieurs conditions

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

Ce que nous avons ici est ...

La ligne 04est un retour explicite [-ish] true car le RHS de &&n'est exécuté que si le LHS était vrai

La ligne 09renvoie soit vrai soit faux correspondant à l'état de la ligne08

La ligne 13renvoie faux à cause de la ligne12

(Oui, cela peut être analysé, mais l'exemple entier est artificiel.)

Un autre modèle courant

# 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

Remarquez comment la définition d'une statusvariable démystifie la signification de $?. (Bien sûr, vous savez ce que cela $?signifie, mais quelqu'un de moins bien informé que vous devrez le faire un jour sur Google. À moins que votre code ne fasse du trading à haute fréquence, montrez un peu d'amour , définissez la variable.) Mais le véritable avantage est que "si pas d'existence d'état "ou inversement" si l'état de sortie "peut être lu à haute voix et expliquer leur signification. Cependant, ce dernier peut être un peu trop ambitieux car voir le mot exitpeut vous faire penser qu'il quitte le script, alors qu'en réalité il quitte le $(...)sous - shell.


** Si vous insistez absolument sur l'utilisation return 1de faux, je vous suggère au moins d'utiliser à la return 255place. Cela amènera votre futur moi-même ou tout autre développeur qui doit maintenir votre code à se demander "pourquoi est-ce 255?" Ensuite, ils feront au moins attention et auront plus de chances d'éviter une erreur.

Bruno Bronosky
la source
1
@ZeroPhase 1 & 0 pour false & true serait ridicule. Si vous avez un type de données binaire, il n'y a aucune raison à cela. Ce que vous traitez dans bash est un code d'état qui reflète le succès (au singulier) et les échecs (au pluriel). C'est "le ifsuccès, faites ceci, elsefaites cela". Succès à quoi? Pourrait vérifier vrai / faux, pourrait vérifier une chaîne, un entier, un fichier, un répertoire, des autorisations d'écriture, glob, regex, grep ou toute autre commande qui est sujette à des échecs .
Bruno Bronosky
3
Éviter l'utilisation standard de 0/1 comme valeur de retour simplement parce qu'il est obtus et sujet à confusion est idiot. L'ensemble du langage shell est obtus et sujet à confusion. Bash lui-même utilise la convention 0/1 = true / false dans ses propres commandes trueet false. C'est-à-dire que le mot clé trueévalue littéralement un code d'état 0. De plus, les déclarations if-then par nature fonctionnent sur des booléens, et non sur des codes de réussite. Si une erreur se produit lors d'une opération booléenne, elle ne doit pas retourner true ou false mais simplement interrompre l'exécution. Sinon, vous obtenez des faux positifs (jeu de mots).
Beejor
1
"if! $ (exit $ status); then" - Cela mérite une meilleure explication. Ce n'est pas intuitif. Je dois penser si le programme va se terminer avant d'imprimer le message, ou non. Le reste de votre réponse était bon, mais cela l'a gâché.
Craig Hicks
1
@BrunoBronosky J'ai lu votre réponse et je pense comprendre la distinction entre le pipeline de contenu (stdin-> stdout) et les codes d'erreur dans les valeurs de retour. Pourtant, je ne comprends pas pourquoi l'utilisation return 1devrait être évitée. En supposant que j'ai une validatefonction, je la trouve raisonnable return 1si la validation échoue. Après tout, c'est une raison pour arrêter l'exécution d'un script s'il n'est pas géré correctement (par exemple, lors de l'utilisation set -e).
JepZ
1
@Jepz, c'est une excellente question. Vous avez raison, c'est return 1valable. Ma préoccupation concerne tous les commentaires disant «0 = vrai 1 = faux est [insérer un mot négatif]». Ces personnes sont susceptibles de lire votre code un jour. Donc pour eux, voir return 255(ou 42 ou même 2) devrait les aider à y penser davantage et à ne pas les confondre avec «vrai». set -el'attrapera toujours.
Bruno Bronosky
31
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes
Kambus
la source
1
Et la négation?
einpoklum
Qu'en est-il, @einpoklum?
Mark Reed
@MarkReed: Je voulais dire, ajoutez le cas "else" à votre exemple.
einpoklum
"chemin" myfun || écho non
Hrobky
13

Soyez prudent lorsque vous vérifiez le répertoire uniquement avec l'option -d!
si la variable $ 1 est vide, la vérification sera toujours réussie. Pour être sûr, vérifiez également que la variable n'est pas vide.

#! /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
RenRen
la source
1
Je ne sais pas comment cela répond à la question posée. Bien qu'il soit agréable de savoir qu'un $ 1 vide peut retourner un vrai lorsqu'il est vide, il ne donne aucune idée de la façon de retourner vrai ou faux à partir d'une fonction bash. Je suggère de créer une nouvelle question "Que se passe-t-il lorsque vous effectuez un test sur une variable de shell vide?" Et puis poster ceci comme réponse.
DRaehal
3
Notez que si vous ajoutez des guillemets appropriés à $1( "$1"), vous n'avez pas besoin de rechercher une variable vide. Le [[ -d "$1" ]]échouerait car ce ""n'est pas un répertoire.
morgents
3

J'ai rencontré un point (pas encore explicitement mentionné?) Sur lequel je trébuchais. Ce n'est pas comment renvoyer le booléen, mais plutôt comment l'évaluer correctement!

J'essayais de dire if [ myfunc ]; then ..., mais c'est tout simplement faux. Vous ne devez pas utiliser les supports! if myfunc; then ...est la façon de le faire.

Comme chez @Bruno et d'autres réitérés, trueet ce falsesont des commandes , pas des valeurs! C'est très important pour comprendre les booléens dans les scripts shell.

Dans cet article, j'ai expliqué et fait une démonstration en utilisant des variables booléennes : https://stackoverflow.com/a/55174008/3220983 . Je suggère fortement de vérifier cela, car il est si étroitement lié.

Ici, je vais fournir quelques exemples de retour et d' évaluation de booléens à partir de fonctions:

Ce:

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

Ne produit aucune sortie d'écho. (ie false retourne faux)

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

Produit:

it is                                                        

(ie true retourne vrai)

Et

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

Produit:

it is                                                                           

Parce que 0 (c'est-à-dire vrai) a été renvoyé implicitement .

Maintenant, c'est ce qui me foutait ...

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

Produit:

it is                                                                           

ET

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

ÉGALEMENT produit:

it is                                                                           

L'utilisation des parenthèses ici a produit un faux positif ! (Je déduis que le résultat de la commande "externe" est 0.)

Le principal point à retenir de mon message est: n'utilisez pas de crochets pour évaluer une fonction booléenne (ou variable) comme vous le feriez pour un contrôle d'égalité typique, par exemple if [ x -eq 1 ]; then...!

BuvinJ
la source
2

Utilisez les commandes trueou falsejuste avant votre return, puis returnsans paramètres. Le returnva automatiquement utiliser la valeur de votre dernière commande.

La fourniture d'arguments à returnest incohérente, spécifique au type et sujette à erreur si vous n'utilisez pas 1 ou 0. Et comme l'ont indiqué les commentaires précédents, utiliser 1 ou 0 ici n'est pas la bonne façon d'approcher cette fonction.

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

Production:

$ bash bash_return.sh

cat:
- True
hat:
- False
mrteatime
la source
1

Cela pourrait fonctionner si vous réécrivez ceci function myfun(){ ... return 0; else return 1; fi;}comme ceci function myfun(){ ... return; else false; fi;}. C'est-à-dire que si falsec'est la dernière instruction de la fonction, vous obtenez un faux résultat pour la fonction entière mais returninterrompt la fonction avec le vrai résultat de toute façon. Je crois que c'est vrai pour mon interprète bash au moins.

Neveu
la source
1

J'ai trouvé que la forme la plus courte pour tester la sortie de la fonction est simplement

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

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

Pour des raisons de lisibilité du code, je pense que renvoyer vrai / faux devrait:

  • être sur une seule ligne
  • être une commande
  • être facile à retenir
  • mentionner le mot-clé returnsuivi d'un autre mot-clé ( trueou false)

Ma solution est return $(true)ou return $(false)comme indiqué:

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

Dans le prolongement de @Bruno Bronosky et @mrteatime, je vous propose d'écrire votre retour booléen "à l'envers". Voici ce que je veux dire:

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

Cela élimine l'exigence laide de deux lignes pour chaque déclaration de retour.

BuvinJ
la source