Comment quitter en cas d'échec d'une commande?

222

Je suis un noob en shell-scripting. Je veux imprimer un message et quitter mon script si une commande échoue. J'ai essayé:

my_command && (echo 'my_command failed; exit)

mais ça ne marche pas. Il continue d'exécuter les instructions suivant cette ligne dans le script. J'utilise Ubuntu et bash.

user459246
la source
5
Avez-vous voulu que la citation non fermée soit une erreur de syntaxe qui entraînerait un échec / une sortie? Sinon, vous devez fermer le devis dans votre exemple.
plaques de cuisson

Réponses:

406

Essayer:

my_command || { echo 'my_command failed' ; exit 1; }

Quatre changements:

  • Remplacer &&par||
  • Utilisation { }à la place de( )
  • Présentez ;après exitet
  • espaces après {et avant}

Puisque vous souhaitez imprimer le message et quitter uniquement lorsque la commande échoue (quitte avec une valeur non nulle), vous avez besoin d'un ||pas un &&.

cmd1 && cmd2

s'exécutera en cmd2cas de cmd1succès (valeur de sortie 0). Tandis que

cmd1 || cmd2

s'exécutera en cmd2cas d' cmd1échec (valeur de sortie non nulle).

Utiliser ( )rend la commande à l'intérieur d'eux exécutable dans un sous-shell et appeler un à exitpartir de là vous oblige à quitter le sous-shell et non votre shell d'origine, donc l'exécution continue dans votre shell d'origine.

Pour surmonter cette utilisation { }

Les deux dernières modifications sont requises par bash.

codaddict
la source
4
Il semble être "inversé". Si une fonction "réussit", elle retourne 0 et si elle "échoue", elle retourne non nulle, donc && pourrait être censé évaluer quand la première moitié est retournée non nulle. Cela ne signifie pas que la réponse ci-dessus est incorrecte - non, elle est correcte. && et || dans les scripts, le travail est basé sur le succès et non sur la valeur de retour.
CashCow
7
Cela semble inversé, mais lisez-le et cela a du sens: "faites cette commande (avec succès)" OU "imprimez cette erreur et quittez"
simpleuser
2
La logique derrière cela est que le langage utilise l'évaluation de court-circuit (SCE). Avec SCE, f l'expression est de la forme "p OR q", et p est évalué comme étant vrai, alors il n'y a même aucune raison de regarder q. Si l'expression est de la forme "p ET q" et que p est évalué comme faux, il n'y a aucune raison de regarder q. La raison en est double: 1) c'est plus rapide, pour des raisons évidentes et 2) cela évite certains types d'erreurs (par exemple: "si x! = 0 ET 10 / x> 5" plantera s'il n'y a pas de SCE ). La possibilité de l'utiliser dans la ligne de commande comme celle-ci est un effet secondaire heureux.
user2635263
2
Je recommanderais de faire écho à STDERR:{ (>&2 echo 'my_command failed') ; exit 1; }
rynop
127

Les autres réponses ont bien couvert la question directe, mais vous pourriez également être intéressé à utiliser set -e. Avec cela, toute commande qui échoue (en dehors de contextes spécifiques comme les iftests) entraînera l'abandon du script. Pour certains scripts, c'est très utile.

Daenyth
la source
3
Malheureusement, c'est aussi très dangereux - set -ea un ensemble de règles longues et obscures sur le moment où cela fonctionne et quand il ne fonctionne pas. (Si quelqu'un s'exécute if yourfunction; then ..., il set -ene tirera jamais à l'intérieur yourfunctionou quoi que ce soit qu'il appelle, car ce code est considéré comme "vérifié"). Voir la section des exercices de BashFAQ # 105 , discutant de cela et d'autres pièges (dont certains ne s'appliquent qu'à des versions de shell spécifiques, vous devez donc être sûr de tester toutes les versions qui pourraient être utilisées pour exécuter votre script).
Charles Duffy
67

Notez également que l'état de sortie de chaque commande est stocké dans la variable shell $ ?, que vous pouvez vérifier immédiatement après l'exécution de la commande. Un état différent de zéro indique un échec:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Alex Howansky
la source
10
Cela peut simplement être remplacé par if my_command, il n'est pas nécessaire d'utiliser la commande test ici.
Bart Sas
5
+1 parce que je pense que vous ne devriez pas être puni pour avoir répertorié cette alternative - elle devrait être sur la table - bien que ce soit un peu moche et selon mon expérience trop facile d'exécuter une autre commande entre les deux sans remarquer la nature du test (peut-être que je '' m juste stupide).
Tony Delroy
1
@BartSas Si la commande est longue, il vaut mieux la mettre sur sa propre ligne. Cela rend le script plus lisible.
Michael
@Michael, pas s'il crée des dépendances entre les lignes, où vous devez savoir ce qui s'est passé sur la ligne A avant de comprendre la ligne B (ou, pire, vous devez savoir ce qui va se passer sur la ligne B avant de savoir qu'il est sûr d'ajouter quelque chose nouveau après la fin de la ligne A). J'ai vu trop de fois quelqu'un ajouter un echo "Just finished step A", ne sachant pas que cela echoécrasait le $?qui allait être vérifié plus tard.
Charles Duffy
67

Si vous voulez ce comportement pour toutes les commandes de votre script, ajoutez simplement

  set -e 
  set -o pipefail

au début du script. Cette paire d'options indique à l'interpréteur bash de quitter chaque fois qu'une commande revient avec un code de sortie différent de zéro.

Cependant, cela ne vous permet pas d'imprimer un message de sortie.

damienfrancois
la source
8
Vous pouvez exécuter des commandes à la sortie à l'aide de la trapcommande intégrée bash.
Gavin Smith
Ceci est la réponse à la question XY.
jwg
5
Cela pourrait être intéressant d'expliquer ce que les commandes font indépendamment plutôt que la paire. D'autant plus set -o pipefailque ce n'est peut-être pas le comportement souhaité. Merci encore de l'avoir signalé!
Antoine Pinsard
3
set -en'est pas du tout fiable, cohérent ou prévisible! Pour vous en convaincre, consultez la section des exercices du BashFAQ # 105 , ou le tableau comparant les comportements des différents obus sur in-ulm.de/~mascheck/various/set-e
Charles Duffy
14

J'ai piraté l'idiome suivant:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Faites précéder chaque commande d'un écho informatif et suivez chaque commande avec la même
if [ $? -ne 0 ];...ligne. (Bien sûr, vous pouvez modifier ce message d'erreur si vous le souhaitez.)

Grant Birchmeier
la source
Cela pourrait être défini comme une fonction, donc la réutiliser.
Eric Wang
1
'si [$? -ne 0]; alors ... fi 'est une manière compliquée d'écrire' || '
kevin cline
if ! javac *.java; then ...- pourquoi s'en préoccuper $??
Charles Duffy
Charles - parce que je suis un scripteur bash médiocre au mieux :)
Grant Birchmeier
10

Fourni my_commandest conçu de manière canonique, c'est -à- dire qu'il renvoie 0 en cas de succès, alors &&c'est exactement le contraire de ce que vous voulez. Tu veux ||.

Notez également que cela (ne me semble pas juste en bash, mais je ne peux pas essayer d'où je suis. Dîtes-moi.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
la source
IIRC, vous avez besoin de points-virgules après chacune des lignes à l'intérieur {}
Alex Howansky
9
@Alex: pas s'ils sont sur une ligne distincte.
pause jusqu'à nouvel ordre.
5

Vous pouvez également utiliser, si vous souhaitez conserver l'état d'erreur de sortie, et avoir un fichier lisible avec une commande par ligne:

my_command1 || exit $?
my_command2 || exit $?

Cependant, cela n'imprimera aucun message d'erreur supplémentaire. Mais dans certains cas, l'erreur sera quand même imprimée par la commande ayant échoué.

alexpirine
la source
1
Il n'y a aucune raison que ce soit le cas exit $?; exitutilise simplement $?comme valeur par défaut.
Charles Duffy
@CharlesDuffy ne savait pas. Génial, c'est encore plus simple!
alexpirine
3

Le trapshell intégré permet d'intercepter des signaux et d'autres conditions utiles, y compris l'exécution échouée de commandes (c'est-à-dire un état de retour non nul). Donc, si vous ne voulez pas tester explicitement le statut de retour de chaque commande, vous pouvez le dire trap "your shell code" ERRet le code shell sera exécuté à chaque fois qu'une commande retourne un statut non nul. Par exemple:

trap "echo script failed; exit 1" ERR

Notez que comme pour d'autres cas de capture de commandes ayant échoué, les pipelines nécessitent un traitement spécial; ce qui précède n'attrapera pas false | true.

kozel
la source