Que signifie set -e dans un script bash?

713

J'étudie le contenu de ce fichier preinst que le script exécute avant que ce paquet ne soit décompressé de son fichier d'archive Debian (.deb).

Le script a le code suivant:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

Ma première requête concerne la ligne:

set -e

Je pense que le reste du script est assez simple: il vérifie si le gestionnaire de paquets Debian / Ubuntu exécute une opération d'installation. Si c'est le cas, il vérifie si mon application vient d'être installée sur le système. Si c'est le cas, le script affiche le message "MyApplicationName vient d'être installé" et se termine (cela return 1signifie que cela se termine par une "erreur", n'est-ce pas?).

Si l'utilisateur demande au système de paquets Debian / Ubuntu d'installer mon paquet, le script supprime également deux répertoires.

Est-ce vrai ou ai-je raté quelque chose?

AndreaNobili
la source
42
set -e
Anders Lindahl
46
raison pour laquelle vous ne pouvez pas trouver cela dans google: -e dans votre requête est interprété comme une négation. Essayez la requête suivante: bash set "-e"
Maleev
3
@twalberg Quand je me suis posé la même question, je regardaisman set
Sedat Kilinc
4
si vous cherchez à le désactiver, remplacez le tiret par un préfixe plus:set +e
Tom Saleeba
@twalberg mais demander de vraies personnes est tellement plus intéressant que de simplement faire une demande à partir d'un robot ;-).
vdegenne

Réponses:

797

De help set:

  -e  Exit immediately if a command exits with a non-zero status.

Mais c'est considéré comme une mauvaise pratique par certains (auteurs bash FAQ et irc freenode #bash FAQ). Il est recommandé d'utiliser:

trap 'do_something' ERR

pour exécuter la do_somethingfonction lorsque des erreurs se produisent.

Voir http://mywiki.wooledge.org/BashFAQ/105

Gilles Quenot
la source
14
Que serait le do_something si je voulais la même sémantique que «Quitter immédiatement si une commande se termine avec un état non nul»?
CMCDragonkai
71
trap 'exit' ERR
chepner
12
Le ERRtrap n'est pas hérité par les fonctions shell, donc si vous avez des fonctions, set -o errtraceou set -Evous permettra de simplement définir le trap une fois et de l'appliquer globalement.
ykay
31
ne trap 'exit' ERRfaire quoi que ce soit différent de set -e?
Andy
22
si c'est une mauvaise pratique, alors pourquoi est-il utilisé dans les paquets Debian ?
phuclv
98

set -earrête l'exécution d'un script si une commande ou un pipeline a une erreur - ce qui est l'opposé du comportement par défaut du shell, qui est d'ignorer les erreurs dans les scripts. Tapez help setun terminal pour voir la documentation de cette commande intégrée.

Robin Green
la source
45
Il arrête l'exécution uniquement si la dernière commande d'un pipeline contient une erreur. Il existe une option spécifique à Bash, set -o pipefailqui peut être utilisée pour propager des erreurs afin que la valeur de retour de la commande de pipeline soit non nulle si l'une des commandes précédentes s'est terminée avec un état non nul.
Anthony Geoghegan
2
Gardez à l'esprit que cela -o pipefailsignifie uniquement que l' état de sortie de la première commande non nulle (c'est-à-dire en -o errexittermes d' erreur ) du pipeline est propagé à la fin. Les commandes restantes dans le pipeline fonctionnent toujours , même avec set -o errexit. Par exemple :, echo success | cat - <(echo piping); echo continuesecho successreprésente une commande réussie, mais faillible, s'imprimera success, pipinget continues, mais false | cat - <(echo piping); echo continues, avec la falsereprésentation de la commande maintenant en erreur silencieusement, s'imprimera toujours pipingavant de quitter.
bb010g
55

Selon bash - Le manuel Set Builtin , si -e/ errexitest défini, le shell se ferme immédiatement si un pipeline composé d'une seule commande simple , d' une liste ou d' une commande composée renvoie un état non nul.

Par défaut, l'état de sortie d'un pipeline est l'état de sortie de la dernière commande du pipeline, sauf si l' pipefailoption est activée (elle est désactivée par défaut).

Si tel est le cas, l'état de retour du pipeline de la dernière commande (la plus à droite) pour quitter avec un état différent de zéro, ou zéro si toutes les commandes se terminent avec succès.

Si vous souhaitez exécuter quelque chose à la sortie, essayez de définir trap, par exemple:

trap onexit EXIT

onexitest votre fonction pour faire quelque chose à la sortie, comme ci-dessous qui imprime la trace de pile simple :

onexit(){ while caller $((n++)); do :; done; }

Il existe une option similaire -E/errtrace qui intercepterait plutôt l'ERR, par exemple:

trap onerr ERR

Exemples

Exemple de statut zéro:

$ true; echo $?
0

Exemple de statut différent de zéro:

$ false; echo $?
1

Exemples de statut négatif:

$ ! false; echo $?
0
$ false || true; echo $?
0

Test avec pipefaildésactivation:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

Test avec pipefailactivation:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
kenorb
la source
55

J'ai trouvé ce message en essayant de comprendre quel était le statut de sortie d'un script abandonné en raison d'un set -e. La réponse ne m'a pas paru évidente; d'où cette réponse. Fondamentalement, set -eabandonne l'exécution d'une commande (par exemple un script shell) et renvoie le code d'état de sortie de la commande qui a échoué (c'est-à-dire le script interne, pas le script externe) .

Par exemple, supposons que j'ai le script shell outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;

Le code pour inner-test.shest:

#!/bin/sh
exit 26;

Lorsque j'exécute à outer-script.shpartir de la ligne de commande, mon script externe se termine avec le code de sortie du script interne:

$ ./outer-test.sh
$ echo $?
26
entpnerd
la source
10

Je crois que l'intention est que le script en question échoue rapidement.

Pour tester cela vous-même, tapez simplement set -eà l'invite bash. Maintenant, essayez de courir ls. Vous obtiendrez une liste de répertoires. Maintenant, tapez lsd. Cette commande n'est pas reconnue et renverra un code d'erreur, et donc votre invite bash se fermera (en raison de set -e).

Maintenant, pour comprendre cela dans le contexte d'un «script», utilisez ce script simple:

#!/bin/bash 
# set -e

lsd 

ls

Si vous l'exécutez tel quel, vous obtiendrez la liste des répertoires lssur la dernière ligne. Si vous décommentez le set -eet réexécutez, vous ne verrez pas la liste des répertoires car bash arrête le traitement une fois qu'il rencontre l'erreur lsd.

Kallin Nagelberg
la source
Cette réponse ajoute-t-elle des idées ou des informations qui n'ont pas déjà été fournies par d'autres sur la question?
Charles Duffy
6
Je pense qu'il offre une explication claire et succincte de la fonctionnalité qui n'est pas présente dans les autres réponses. Rien de plus, juste plus concentré que les autres réponses.
Kallin Nagelberg
9

C'est une vieille question, mais aucune des réponses ici ne traite de l'utilisation de set -eaka set -o errexitdans les scripts de gestion des paquets Debian. L'utilisation de cette option est obligatoire dans ces scripts, selon la politique Debian; l'intention est apparemment d'éviter toute possibilité de condition d'erreur non gérée.

En pratique, cela signifie que vous devez comprendre dans quelles conditions les commandes que vous exécutez peuvent renvoyer une erreur et gérer chacune de ces erreurs de manière explicite.

Les pièges courants sont par exemple diff(retourne une erreur quand il y a une différence) et grep(retourne une erreur quand il n'y a pas de correspondance). Vous pouvez éviter les erreurs avec une gestion explicite:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(Remarquez également comment nous prenons soin d'inclure le nom du script actuel dans le message et d'écrire des messages de diagnostic sur l'erreur standard au lieu de la sortie standard.)

Si aucune manipulation explicite n'est vraiment nécessaire ou utile, ne faites rien explicitement:

diff this that || true
grep cat food || :

(L'utilisation de la :commande no-op du shell est légèrement obscure, mais assez courante.)

Juste pour réitérer,

something || other

est un raccourci pour

if something; then
    : nothing
else
    other
fi

c'est-à-dire que nous disons explicitement otherdevrait être exécuté si et seulement si somethingéchoue. Le longhand if(et d'autres instructions de contrôle de flux shell comme while, until) est également un moyen valide de gérer une erreur (en effet, si ce n'était pas le cas, les scripts shell avec set -ene pourraient jamais contenir d'instructions de contrôle de flux!)

Et aussi, juste pour être explicite, en l'absence d'un gestionnaire comme celui-ci, set -ele script entier échouerait immédiatement avec une erreur s'il difftrouvait une différence ou s'il grepne trouvait pas de correspondance.

D'un autre côté, certaines commandes ne produisent pas d'état de sortie d'erreur lorsque vous le souhaitez. Les commandes généralement problématiques sont find(l'état de sortie ne reflète pas si les fichiers ont été réellement trouvés) et sed(l'état de sortie ne révélera pas si le script a reçu une entrée ou s'il a effectivement exécuté des commandes avec succès). Un simple garde dans certains scénarios consiste à diriger vers une commande qui crie s'il n'y a pas de sortie:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

Il convient de noter que l'état de sortie d'un pipeline est l'état de sortie de la dernière commande de ce pipeline. Les commandes ci-dessus masquent donc complètement l'état de findet sed, et vous indiquent uniquement si vous avez grepfinalement réussi.

(Bash, bien sûr, set -o pipefailmais les scripts de paquets Debian ne peuvent pas utiliser les fonctionnalités de Bash. La politique dicte fermement l'utilisation de POSIX shpour ces scripts, bien que ce ne soit pas toujours le cas.)

Dans de nombreuses situations, c'est quelque chose à surveiller séparément lors du codage défensif. Parfois, vous devez par exemple parcourir un fichier temporaire pour voir si la commande qui a produit cette sortie s'est terminée avec succès, même si l'idiome et la commodité vous inciteraient autrement à utiliser un pipeline shell.

tripleee
la source
c'est une excellente réponse. et il promeut les meilleures pratiques. J'ai eu exactement le même problème avec la commande GREP et je ne voulais vraiment pas supprimer le 'set -e'
Minnie
7
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further
Manikandan Raj
la source