Abandonner un script shell si une commande renvoie une valeur non nulle?

437

J'ai un script shell Bash qui appelle un certain nombre de commandes. Je voudrais que le script shell se termine 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?

par exemple

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

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi
Jin Kim
la source
8
En plus de set -e, faites également set -u(ou set -eu). -umet fin au comportement idiot de masquage de bogues auquel vous pouvez accéder à n'importe quelle variable inexistante et produire une valeur vide sans diagnostic.
Kaz

Réponses:

742

Ajoutez ceci au début du script:

set -e

Cela provoquera la fermeture immédiate du shell si une simple commande se termine avec une valeur de sortie différente de zéro. Une commande simple est une commande ne faisant pas partie d'un test if, while ou until, ou faisant partie d'un && ou || liste.

Voir la page de manuel bash (1) sur la commande interne "set" pour plus de détails.

Personnellement, je démarre presque tous les scripts shell avec "set -e". C'est vraiment ennuyeux de voir un script continuer obstinément quand quelque chose échoue au milieu et casse les hypothèses pour le reste du script.

Ville Laurikari
la source
36
Cela fonctionnerait, mais j'aime utiliser "#! / Usr / bin / env bash" car j'exécute fréquemment bash depuis un emplacement autre que / bin. Et "#! / Usr / bin / env bash -e" ne fonctionne pas. En plus, c'est bien d'avoir un endroit à modifier pour lire "set -xe" quand je veux activer le suivi pour le débogage.
Ville Laurikari
48
De plus, les indicateurs sur la ligne shebang sont ignorés si un script est exécuté en tant que bash script.sh.
Tom Anderson
27
Juste une note: si vous déclarez des fonctions à l'intérieur du script bash, les fonctions devront avoir défini -e redéclaré à l'intérieur du corps de la fonction si vous souhaitez étendre cette fonctionnalité.
Jin Kim
8
De plus, si vous sourcez votre script, la ligne shebang ne sera pas pertinente.
4
@JinKim Cela ne semble pas être le cas dans bash 3.2.48. Essayez ce qui suit dans un script: set -e; tf() { false; }; tf; echo 'still here'. Même sans l' set -eintérieur du corps de tf(), l'exécution est interrompue. Peut-être vouliez-vous dire que ce set -en'est pas hérité des sous - coquilles , ce qui est vrai.
mklement0
202

Pour ajouter à la réponse acceptée:

Gardez à l'esprit que set -eparfois cela ne suffit pas, surtout si vous avez des tuyaux.

Par exemple, supposons que vous ayez ce script

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

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

Demain, vous effectuez un changement apparemment insignifiant:

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

... et maintenant ça ne marche pas. Ceci est expliqué ici et une solution de contournement (Bash uniquement) est fournie:

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

./configure | tee configure.log
faire
leonbloy
la source
1
Merci d'avoir expliqué l'importance de devoir pipefailaccepter set -o!
Malcolm
83

Les instructions if de votre exemple ne sont pas nécessaires. Faites comme ça:

dosomething1 || exit 1

Si vous suivez les conseils de Ville Laurikari et utilisez set -ealors pour certaines commandes, vous devrez peut-être utiliser ceci:

dosomething || true

Le || truefera que le pipeline de commandes aura une truevaleur de retour même si la commande échoue, donc l' -eoption ne tuera pas le script.

Zan Lynx
la source
1
J'aime ça. Surtout parce que la première réponse est centrée sur bash (pas du tout clair pour moi si / dans quelle mesure elle s'applique aux scripts zsh). Et je pourrais le chercher, mais votre est plus clair, parce que la logique.
g33kz0r
set -en'est pas centré sur bash - il est pris en charge même sur le Bourne Shell d'origine.
Marcos Vives Del Sol
27

Si vous avez un nettoyage à faire à 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 lance ERR si une commande se termine avec une valeur différente de zéro:

# 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 pouvez intercepter EXIT; votre piège sera alors exécuté lorsque le script se fermera pour une raison quelconque, y compris une fin normale, des interruptions, une sortie provoquée par l'option -e, etc.

fholo
la source
12

La $?variable est rarement nécessaire. Le pseudo-idiome command; if [ $? -eq 0 ]; then X; fidoit toujours s'écrire if command; then X; fi.

Les cas où cela $?est requis, c'est quand il doit être comparé à plusieurs valeurs:

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

ou lorsqu'il $?doit être réutilisé ou manipulé d'une autre manière:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi
Mark Edgar
la source
3
Pourquoi "devrait toujours être écrit comme"? Je veux dire, pourquoi " devrait " en être ainsi? Lorsqu'une commande est longue (pensez à invoquer GCC avec une douzaine d'options), il est alors beaucoup plus lisible d'exécuter la commande avant de vérifier l'état de retour.
ysap
Si une commande est trop longue, vous pouvez la décomposer en la nommant (définir une fonction shell).
Mark Edgar
12

Exécutez-le avec -eou set -een haut.

Regardez aussi set -u.

lumpynose
la source
34
Pour sauver potentiellement les autres, il est nécessaire de lire help set: -utraite les références aux variables non définies comme des erreurs.
mklement0
1
c'est donc soit set -uou set -epas les deux? @lumpynose
ericn
1
@eric J'ai pris ma retraite il y a plusieurs années. Même si j'aimais 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
3

Une expression comme

dosomething1 && dosomething2 && dosomething3

arrêtera le traitement lorsque l'une des commandes reviendra avec une valeur non nulle. Par exemple, la commande suivante n'imprimera jamais "terminé":

cat nosuchfile && echo "done"
echo $?
1
gabor
la source
2
#!/bin/bash -e

devrait suffire.

Baligh Uddin
la source
-2

j'en ai juste ajouté un autre à titre de référence car il y avait une question supplémentaire à l'entrée de Mark Edgars et voici un exemple supplémentaire et touche le sujet dans son ensemble:

[[ `cmd` ]] && echo success_else_silence

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

par exemple. Je veux m'assurer qu'une partition est démontée si elle est montée:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
Malina
la source
5
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 l'état de sortie de la commande.
Gilles 'SO- arrête d'être méchant'