2009-05-04 9 views
306

J'ai un script shell Bash qui appelle un certain nombre de commandes. Je voudrais que le script shell quitte automatiquement avec une valeur de retour de 1 si l'une des commandes renvoie une valeur non nulle. Est-ce possible sans vérifier explicitement le résultat de chaque commande?Annulation d'un script shell si une commande renvoie une valeur non nulle?

par exemple.

dosomething1 
if [[ $? -ne 0 ]]; then 
    exit 1 
fi 

dosomething2 
if [[ $? -ne 0 ]]; then 
    exit 1 
fi 
+5

En plus de 'set -e', faites aussi' set -u' (ou 'set -eu'). '-u' met un terme à ce comportement idiot de dissimulation de bogues qui vous permet d'accéder à n'importe quelle variable inexistante et d'obtenir une valeur vide sans aucun diagnostic. – Kaz

Répondre

550

Ajouter ce au début du script:

set -e 

Cela entraînera la coquille pour sortir immédiatement si une simple commande se termine avec une valeur de sortie non nulle. Une commande simple est une commande qui ne fait pas partie d'un test if, while ou until, ou une partie d'un & & ou || liste. Voir le bash(1) man page sur la commande interne "set" pour plus de détails.

Je commence personnellement presque tous les scripts shell avec "set -e". C'est vraiment embêtant d'avoir un script obstinément continuer quand quelque chose échoue au milieu et rompt les hypothèses pour le reste du script.

+32

Cela fonctionnerait, mais j'aime utiliser "#!/Usr/bin/env bash" parce que je lance fréquemment bash depuis un autre endroit que/bin. Et "#!/Usr/bin/env bash -e" ne fonctionne pas. En plus, c'est sympa d'avoir un endroit à modifier pour lire "set -xe" quand je veux activer le traçage pour le débogage. –

+2

Gardez à l'esprit que cela pourrait ne pas être suffisant à cause des pipelines. Voir ma réponse ajoutée. – leonbloy

+45

De plus, les drapeaux de la ligne shebang sont ignorés si un script est exécuté en tant que 'bash script.sh'. –

3

Une expression comme

dosomething1 && dosomething2 && dosomething3 

va arrêter le traitement lorsque l'une des commandes renvoie une valeur non nulle. Par exemple, la commande suivante ne sera jamais imprimer « fait »:

cat nosuchfile && echo "done" 
echo $? 
1 
12

Exécuter avec -e ou set -e en haut.

Regardez également set -u.

+26

Pour potentiellement sauver les autres, il est nécessaire de lire 'help set':' -u' traite les références aux variables non définies comme des erreurs. – mklement0

+1

donc c'est soit 'set -u' ou' set -e', pas les deux? @lumpynose – ericn

+1

@eric J'ai pris ma retraite il y a plusieurs années. Même si j'ai aimé mon travail, mon cerveau âgé a tout oublié. Je suppose que vous pourriez utiliser les deux ensemble; mauvaise formulation de ma part; J'aurais dû dire "et/ou". – lumpynose

64

Les instructions if de votre exemple sont inutiles. Il suffit de le faire comme ceci:

dosomething1 || exit 1 

Si vous prenez les conseils de Ville Laurikari et utilisez set -e puis pour certaines commandes que vous devrez peut-être utiliser:

dosomething || true 

Le || true fera le pipeline de commande un true retourne la valeur même si la commande échoue donc l'option -e ne va pas tuer le script.

+1

J'aime ça. Surtout parce que la première réponse est bash-centrique (pas du tout clair si/dans quelle mesure cela s'applique aux scripts zsh). Et je pourrais le chercher, mais votre est juste plus clair, parce que la logique. – g33kz0r

23

Si vous avez besoin d'un nettoyage à la sortie, vous pouvez également utiliser 'trap' avec le pseudo-signal ERR. Cela fonctionne de la même manière que le piégeage INT ou tout autre signal; bash lancers francs ERR si des sorties de commande avec une valeur non nulle:

# Create the trap with 
# trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...] 
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM 
command1 
command2 
command3 
# Partially turn off the trap. 
trap - ERR 
# Now a control-C will still cause cleanup, but 
# a nonzero exit code won't: 
ps aux | grep blahblahblah 

Ou, surtout si vous utilisez « set -e », vous pourriez piège EXIT; votre piège sera alors exécuté lorsque le script se termine pour une raison quelconque, y compris une fin normale, des interruptions, une sortie provoquée par l'option -e, etc.

8

La variable $? est rarement nécessaire. Le pseudo-idiome command; if [ $? -eq 0 ]; then X; fi doit toujours être écrit if command; then X; fi.

Les cas où $? est requis est quand il doit être vérifié contre plusieurs valeurs:

command 
case $? in 
    (0) X;; 
    (1) Y;; 
    (2) Z;; 
esac 

ou lorsque $? a besoin d'être réutilisés ou manipulés:

if command; then 
    echo "command successful" >&2 
else 
    ret=$? 
    echo "command failed with exit code $ret" >&2 
    exit $ret 
fi 
+1

Pourquoi "devrait toujours être écrit comme"? Je veux dire, pourquoi "* devrait *" l'être? Quand une commande est longue (pensez à appeler GCC avec une douzaine d'options), il est beaucoup plus lisible d'exécuter la commande avant de vérifier l'état de retour. – ysap

+0

Si une commande est trop longue, vous pouvez la décomposer en la nommant (définissez une fonction shell). –

138

Pour ajouter à la réponse acceptée:

Gardez à l'esprit que set -e parfois ne suffit pas, surtout si vous avez pip es.

Par exemple, supposons que vous avez ce script

#!/bin/bash 
set -e 
./configure > configure.log 
make 

... qui fonctionne comme prévu: une erreur dans l'exécution configure avorte.

Demain vous faire un changement apparemment trivial:

#!/bin/bash 
set -e 
./configure | tee configure.log 
make 

... et maintenant il ne fonctionne pas. Ceci est expliqué here, et une solution de contournement (Bash uniquement) est fourni:

 
#!/bin/bash 
set -e 
set -o pipefail 

./configure | tee configure.log 
make 
-2

juste jeter un autre pour référence car il y avait une autre question à l'entrée Mark Edgars et est ici un exemple supplémentaire et touche sur le sujet général:

[[ `cmd` ]] && echo success_else_silence 

qui est le même que cmd || exit errcode que quelqu'un a montré.

par ex. Je veux vous assurer une partition est démonté si elle est montée:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
+3

Non, '[[' cmd']] 'n'est pas la même chose. C'est faux si la sortie de la commande est vide et vrai sinon, quel que soit le statut de sortie de la commande. – Gilles

2
#!/bin/bash -e 

devrait suffire.

Questions connexes