Dans un script Bash, comment puis-je quitter l'intégralité du script si une certaine condition se produit?

717

J'écris un script dans Bash pour tester du code. Cependant, il semble idiot d'exécuter les tests si la compilation du code échoue en premier lieu, auquel cas je vais simplement abandonner les tests.

Existe-t-il un moyen de le faire sans encapsuler l'intégralité du script dans une boucle while et utiliser des pauses? Quelque chose comme un dun dun dun goto?

samoz
la source

Réponses:

810

Essayez cette déclaration:

exit 1

Remplacez-le 1par des codes d'erreur appropriés. Voir aussi Codes de sortie avec des significations spéciales .

Michael Foukarakis
la source
4
@CMCDragonkai, généralement tout code différent de zéro fonctionnera. Si vous n'avez besoin de rien de spécial, vous pouvez simplement l'utiliser de manière 1cohérente. Si le script est destiné à être exécuté par un autre script, vous souhaiterez peut-être définir votre propre ensemble de codes d'état avec une signification particulière. Par exemple, 1== les tests ont échoué, 2== la compilation a échoué. Si le script fait partie d'autre chose, vous devrez peut-être ajuster les codes pour qu'ils correspondent aux pratiques utilisées. Par exemple, lorsqu'une partie de la suite de tests est exécutée par automake, le code 77est utilisé pour marquer un test ignoré.
Michał Górny
21
non, cela ferme aussi la fenêtre, pas seulement quitter le script
Toni Leigh
7
@ToniLeigh bash n'a pas le concept d'une "fenêtre", vous êtes probablement confus quant à ce que fait votre configuration particulière - par exemple un émulateur de terminal -.
Michael Foukarakis
4
@ToniLeigh S'il ferme la "fenêtre", vous placez probablement la exit #commande dans une fonction, pas dans un script. (Dans ce cas, utilisez return #plutôt.)
Jamie
1
@Sevenearths 0 signifie qu'il a réussi, exit 0quittez donc le script et renvoie 0 (indiquant aux autres scripts qui pourraient utiliser le résultat de ce script qu'il a réussi)
VictorGalisson
689

Utilisez set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

Le script se terminera après la première ligne qui échoue (retourne un code de sortie différent de zéro). Dans ce cas, la commande qui échoue2 ne s'exécutera pas.

Si vous deviez vérifier l'état de retour de chaque commande, votre script ressemblerait à ceci:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

Avec set -e, cela ressemblerait à:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Toute commande qui échoue entraînera l'échec de tout le script et renverra un état de sortie que vous pouvez vérifier avec $? . Si votre script est très long ou que vous construisez beaucoup de choses, cela deviendra assez moche si vous ajoutez des contrôles d'état de retour partout.

Shizzmo
la source
10
Avec set -evous encore peut faire une sortie de commandes avec des erreurs sans arrêter le script: command 2>&1 || echo $?.
Adobe
6
set -eabandonnera le script si un pipeline ou une structure de commande renvoie une valeur non nulle. Par exemple foo || bar, échouera uniquement si les deux fooet barrenvoient une valeur non nulle. Habituellement, un script bash bien écrit fonctionnera si vous ajoutez set -eau début et l'addition fonctionne comme un test de validité automatisé: abandonnez le script si quelque chose se passe mal.
Mikko Rantalainen
6
Si vous dirigez des commandes ensemble, vous pouvez également échouer si l'une d'elles échoue en définissant l' set -o pipefailoption.
Jake Biesinger
18
En fait, le code idiomatique sans set -eserait juste make || exit $?.
tripleee
4
Vous aussi set -u. Jetez un oeil à l' officieux mode strict bash : set -euo pipefail.
Pablo A
236

Un gars de SysOps m'a une fois appris la technique de la griffe à trois doigts:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Ces fonctions sont * NIX OS et shell-robustes. Mettez-les au début de votre script (bash ou autre),try() votre déclaration et votre code.

Explication

(basé sur le commentaire d'un mouton volant ).

  • yell: affiche le nom du script et tous les arguments dans stderr:
    • $0 est le chemin d'accès au script;
    • $* sont tous des arguments.
    • >&2signifie >rediriger stdout vers & pipe2 . la pipe1 serait stdoutelle-même.
  • diefait la même chose que yell, mais quitte avec un état de sortie différent de 0 , ce qui signifie «échouer».
  • tryutilise le ||(booléenOR ), qui n'évalue le côté droit que si celui de gauche a échoué.
    • $@est à nouveau tous les arguments, mais différents .
c.gutierrez
la source
3
Je suis assez nouveau dans les scripts Unix. Pouvez-vous expliquer comment les fonctions ci-dessus sont exécutées? Je vois une nouvelle syntaxe que je ne connais pas. Merci.
kaizenCoder
15
crier : $0est le chemin d'accès au script. $*sont tous des arguments. >&2signifie " >rediriger la sortie standard vers le &tuyau 2". le tuyau 1 serait lui-même standard. donc yell préfixe tous les arguments avec le nom du script et les imprime sur stderr. die fait la même chose que crier , mais sort avec un statut de sortie différent de 0, ce qui signifie «échouer». try utilise le booléen ou ||, qui n'évalue le côté droit que si celui de gauche n'a pas échoué. $@est à nouveau tous les arguments, mais différents . espérons que cela explique tout
voler des moutons
1
J'ai modifié cela pour die() { yell "$1"; exit $2; }que vous puissiez passer un message et quitter le code avec die "divide by zero" 115.
Mark Lakata
3
Je peux voir comment j'utiliserais yellet die. Mais trypas tant que ça. Pouvez-vous fournir un exemple de votre utilisation?
kshenoy
2
Hmm, mais comment les utilisez-vous dans un script? Je ne comprends pas ce que vous entendez par "essayez () votre déclaration et votre code".
TheJavaGuy-Ivan Milosavljević
33

Si vous appelez le script avec source, vous pouvez utiliser return <x><x>sera l'état de sortie du script (utilisez une valeur non nulle pour erreur ou faux). Mais si vous invoquez un script exécutable (c'est-à-dire directement avec son nom de fichier), l'instruction return entraînera une réclamation (message d'erreur "return: ne peut" retourner "qu'à partir d'une fonction ou d'un script source").

Si exit <x>est utilisé à la place, lorsque le script est invoqué avec source, il entraînera la fermeture du shell qui a démarré le script, mais un script exécutable se terminera comme prévu.

Pour gérer l'un ou l'autre cas dans le même script, vous pouvez utiliser

return <x> 2> /dev/null || exit <x>

Cela gérera toute invocation appropriée. Cela suppose que vous utiliserez cette instruction au niveau supérieur du script. Je déconseille de quitter directement le script depuis une fonction.

Remarque: <x>est censé être juste un nombre.

kavadias
la source
Ne fonctionne pas pour moi dans un script avec un retour / sortie à l'intérieur d'une fonction, c'est-à-dire est-il possible de sortir à l'intérieur de la fonction sans exister le shell, mais sans faire en sorte que l'appelant se soucie de la vérification correcte du code retour de la fonction ?
jan
@jan Ce que l'appelant fait (ou ne fait pas) avec les valeurs de retour, est complètement orthogonal (c'est-à-dire indépendant de) la façon dont vous revenez de la fonction (... sans quitter le shell, quelle que soit l'invocation). Cela dépend principalement du code de l'appelant, qui ne fait pas partie de cette Q&R. Vous pouvez même adapter la valeur de retour de la fonction aux besoins de l'appelant, mais cette réponse ne limite pas ce que peut être cette valeur de retour ...
kavadias
11

J'inclus souvent une fonction appelée run () pour gérer les erreurs. Chaque appel que je veux faire est passé à cette fonction, donc le script entier se ferme lorsqu'un échec est rencontré. L'avantage de ceci par rapport à la solution set -e est que le script ne se ferme pas silencieusement lorsqu'une ligne échoue et peut vous dire quel est le problème. Dans l'exemple suivant, la 3ème ligne n'est pas exécutée car le script se ferme lors de l'appel à false.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"
Joseph Sheedy
la source
1
Mec, pour une raison quelconque, j'aime vraiment cette réponse. Je reconnais que c'est un peu plus compliqué, mais cela semble si utile. Et étant donné que je ne suis pas un expert bash, cela me porte à croire que ma logique est défectueuse, et il y a quelque chose de mal avec cette méthodologie, sinon, je pense que d'autres l'auraient fait plus d'éloges. Alors, quel est le problème avec cette fonction? Y a-t-il quelque chose que je devrais rechercher ici?
Je ne me souviens pas de la raison pour laquelle j'utilise eval, la fonction fonctionne très bien avec cmd_output = $ ($ 1)
Joseph Sheedy
Je viens de l'implémenter dans le cadre d'un processus de déploiement complexe et cela a fonctionné de manière fantastique. Merci et voici un commentaire et une upvote.
fuzzygroup
Un travail vraiment incroyable! Il s'agit de la solution la plus simple et la plus propre qui fonctionne bien. Pour moi, j'ai ajouté ceci avant une commande dans une boucle FOR car les boucles FOR ne capteront pas le set -e option. Ensuite , la commande, car il est avec des arguments, j'ai utilisé des guillemets simples pour éviter les problèmes de bash comme ceci: runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Remarque J'ai changé le nom de la fonction en runTry.
Tony-Caffe
1
evalest potentiellement dangereux si vous acceptez des entrées arbitraires, mais sinon cela a l'air plutôt bien.
dragon788
5

Au lieu de ifconstruire, vous pouvez tirer parti de l' évaluation de court-circuit :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Notez la paire de parenthèses qui est nécessaire en raison de la priorité de l'opérateur d'alternance. $?est une variable spéciale définie pour quitter le code de la dernière commande appelée.

skalee
la source
1
si je fais command -that --fails || exit $?ça fonctionne sans parenthèses, qu'est-ce echo $[4/0]qui nous amène à en avoir besoin?
Anentropic
2
@Anentropic @skalee Les parenthèses n'ont rien à voir avec la priorité, mais la gestion des exceptions. Une division par zéro entraînera une sortie immédiate du shell avec le code 1. Sans les parenthèses (c'est-à-dire, un simple echo $[4/0] || exit $?) bash n'exécutera jamais le echo, sans parler d'obéir au ||.
bobbogo
1

J'ai la même question mais je ne peux pas la poser car ce serait un doublon.

La réponse acceptée, en utilisant exit, ne fonctionne pas lorsque le script est un peu plus compliqué. Si vous utilisez un processus d'arrière-plan pour vérifier la condition, exit ne quitte que ce processus, car il s'exécute dans un sous-shell. Pour tuer le script, vous devez le tuer explicitement (du moins c'est la seule façon que je connaisse).

Voici un petit script sur la façon de le faire:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

C'est une meilleure réponse mais toujours incomplète. Je ne sais vraiment pas comment me débarrasser de la partie de la flèche .

Géo Trouvetou
la source