J'observe un comportement étrange lors de l'utilisation de set -e
( errexit
), set -u
( nounset
) avec les pièges ERR et EXIT. Ils semblent liés, donc les poser dans une question semble raisonnable.
1) set -u
ne déclenche pas de pièges ERR
Code:
#!/bin/bash trap 'echo "ERR (rc: $?)"' ERR set -u echo ${UNSET_VAR}
- Attendu: le piège ERR est appelé, RC! = 0
- Réel: l'interruption ERR n'est pas appelée, RC == 1
- Remarque:
set -e
ne modifie pas le résultat
2) L'utilisation set -eu
du code de sortie dans une interruption EXIT vaut 0 au lieu de 1
Code:
#!/bin/bash trap 'echo "EXIT (rc: $?)"' EXIT set -eu echo ${UNSET_VAR}
- Attendu: le piège EXIT est appelé, RC == 1
- Réel: le trap EXIT est appelé, RC == 0
- Remarque: Lors de l'utilisation
set +e
, le RC == 1. Le piège EXIT renvoie le RC approprié lorsqu'une autre commande génère une erreur. - Edit: Il y a un article SO sur ce sujet avec un commentaire intéressant suggérant que cela pourrait être lié à la version Bash utilisée. Le test de cet extrait avec Bash 4.3.11 donne un RC = 1, donc c'est mieux. Malheureusement, la mise à niveau de Bash (à partir de 3.2.51) sur tous les hôtes n'est pas possible pour le moment, nous devons donc trouver une autre solution.
Quelqu'un peut-il expliquer l'un ou l'autre de ces comportements?
La recherche sur ces sujets n'a pas été très réussie, ce qui est plutôt surprenant compte tenu du nombre de publications sur les paramètres et les pièges de Bash. Il y a un fil de discussion , cependant, mais la conclusion est plutôt insatisfaisante.
bash
rompu avec la norme et commencé à mettre des pièges en sous-coquilles. Le piège est censé être exécuté dans le même environnement d'où le retour, maisbash
il ne l'a pas fait depuis un certain temps.set -e
etset -u
sont tous deux conçus spécifiquement pour tuer un shell scripté. Les utiliser dans des conditions susceptibles de déclencher leur application tuera un shell scripté. Il n'y a pas moyen de contourner cela, sauf pour ne pas les utiliser, et plutôt pour tester ces conditions lorsqu'elles s'appliquent dans une séquence de code. Donc, fondamentalement, vous pouvez écrire un bon code shell, ou vous pouvez utiliserset -eu
.-u
ne déclencherait pas le piège ERR (c'est une erreur, alors ne devrait-il pas déclencher le piège) ou le code d'erreur est 0 au lieu de 1. Le ce dernier semble être un bug qui a déjà été corrigé dans une version ultérieure, c'est tout. Mais la première partie est assez difficile à comprendre si vous n'avez pas réalisé que les erreurs dans l'évaluation du shell (expansion des paramètres) et les erreurs réelles dans les commandes semblent être deux choses différentes. Pour la solution, eh bien, comme vous l'avez suggéré, j'essaie maintenant d'éviter-eu
et de vérifier manuellement quand c'est nécessaire.(set -u; : $UNSET_VAR)
et similaire. Ce genre de trucs peut aussi être bon - vous pouvez en déposer beaucoup de&&
temps en temps:(set -e; mkdir dir; cd dir; touch dirfile)
si vous obtenez ma dérive. C'est juste que ce sont des contextes contrôlés - lorsque vous les définissez comme des options globales, vous perdez le contrôle et devenez contrôlé. Cependant, il existe généralement des solutions plus efficaces.Réponses:
De
man bash
:set -u
"@"
et"*"
comme une erreur lors de l'expansion des paramètres. Si une expansion est tentée sur une variable ou un paramètre non-i
défini, le shell affiche un message d'erreur et, s'il n'est pas interactif, se termine avec un état différent de zéro.POSIX indique que, en cas d' erreur d'expansion , un shell non interactif doit quitter lorsque l'expansion est associée soit à un shell spécial intégré (ce qui est une distinction
bash
régulièrement ignoré de toute façon, et donc peut-être pas pertinent) ou à tout autre utilitaire en plus ."${x!y}"
, car!
n'est pas un opérateur valide) ; une implémentation peut les traiter comme des erreurs de syntaxe si elle est capable de les détecter lors de la tokenisation, plutôt que lors de l'expansion.Aussi de
man bash
:trap ... ERR
while
oruntil
...if
déclaration ...&&
ou||
à l'exception de la commande suivant la finale&&
ou||
...!
.-e
.Notez ci-dessus que le piège ERR concerne l'évaluation du retour d'une autre commande. Mais lorsqu'une erreur d'extension se produit, aucune commande n'est exécutée pour renvoyer quoi que ce soit. Dans votre exemple, cela
echo
ne se produit jamais - car pendant que le shell évalue et développe ses arguments, il rencontre une-u
variable nset, qui a été spécifiée par l'option de shell explicite pour provoquer une sortie immédiate du shell scripté actuel.Ainsi, le piège EXIT , le cas échéant, est exécuté, et le shell se termine avec un message de diagnostic et un état de sortie autre que 0 - exactement comme il devrait le faire.
Quant au rc: 0 chose, je pense que c'est un bug spécifique version de quelque sorte - sans doute à voir avec les deux éléments déclencheurs de la EXIT se produisant en même temps et celui d' obtenir le code de sortie de l'autre (qui ne devrait pas se produire) . Et de toute façon, avec un
bash
binaire à jour installé parpacman
:J'ai ajouté la première ligne pour que vous puissiez voir que les conditions du shell sont celles d'un shell scripté - ce n'est pas interactif. La sortie est:
Voici quelques notes pertinentes des changelogs récents :
$?
correctement.for
commandes avaient le mauvais numéro de ligne.trap
pable dans les commandes de sous-shell asynchrones.trap
gestionnaires pour ces signaux et permet à la plupart destrap
gestionnaires d'être exécutés de manière récursive (exécution detrap
gestionnaires pendant l'trap
exécution d' un gestionnaire) .Je pense que c'est le dernier ou le premier qui est le plus pertinent - ou peut-être une combinaison des deux. Un
trap
gestionnaire est par nature asynchrone car son travail consiste à attendre et à gérer les signaux asynchrones . Et vous déclenchez deux simultanément avec-eu
et$UNSET_VAR
.Et peut-être que vous devriez simplement mettre à jour, mais si vous vous aimez, vous le ferez avec un shell différent.
la source
(J'utilise bash 4.2.53). Pour la partie 1, la page de manuel bash indique simplement "Un message d'erreur sera écrit dans l'erreur standard et un shell non interactif se fermera". Cela ne dit pas qu'un piège ERR sera appelé, bien que je convienne que ce serait utile s'il le faisait.
Pour être pragmatique, si ce que vous voulez vraiment, c'est gérer plus proprement les variables non définies, une solution possible est de mettre la plupart de votre code à l'intérieur d'une fonction, puis d'exécuter cette fonction dans un sous-shell et de récupérer le code retour et la sortie stderr. Voici un exemple où "cmd ()" est la fonction:
Sur mon bash je reçois
la source