J'observe un comportement étrange lorsque vous utilisez set -e
(errexit
), set -u
(nounset
) ainsi que des pièges à la sortie. Ils semblent liés, alors les mettre dans une seule question semble raisonnable.
set -u
ne déclenche pas les pièges à mépriserCode:
#!/bin/bash
trap 'echo "ERR (rc: $?)"' ERR
set -u
echo ${UNSET_VAR}
set -e
ne change pas le résultatset -eu
Le code de sortie dans un piège de sortie est 0 au lieu de 1Code:
#!/bin/bash
trap 'echo "EXIT (rc: $?)"' EXIT
set -eu
echo ${UNSET_VAR}
set +e
, le RC == 1. Le piège de sortie renvoie le bon RC lorsque toute autre commande jette une erreur.Quelqu'un peut-il expliquer l'un de ces comportements?
La recherche de ces sujets n'a pas été très réussie, ce qui est plutôt surprenant compte tenu du nombre de postes sur les paramètres de bash et les pièges. Il y a n fil de forum , mais la conclusion est plutôt insatisfaisante.
De man bash
:
set -u
"@"
et "*"
comme une erreur lors de l'expansion des paramètres. Si l'expansion est tentée sur une variable ou un paramètre non défini, le shell imprime un message d'erreur et, sinon -i
Trontactive, sort avec un statut non nul.POSIX indique que, dans le cas d'une erreur erreur d'expansion, une coque non interactive doit quitter Lorsque l'expansion est associée à une coque spéciale intégrée (qui est une distinction bash
ignore régulièrement de toute façon, et peut-être peut-être non négligeant) ou tout autre utilitaire en plus.
"${x!y}"
, car !
n'est pas un opérateur valide); Une implémentation Peut-être Traitez-les comme des erreurs de syntaxe s'il est capable de les détecter pendant la jonction, plutôt que pendant l'expansion.Aussi de man bash
:
trap ... ERR
while
ou until
mot-clé...if
...&&
ou ||
liste sauf la commande suivant la finale &&
ou ||
...!
.-e
option.Notez ci-dessus que le [~ # ~ ~] ERR [~ # ~] Piège est tout sur l'évaluation de certains Autre Retour de la commande. Mais quand Erreur d'expansion On se produit, il n'y a pas de commande à retourner quoi que ce soit. Dans votre exemple, echo
N'arrive jamais - parce que pendant que la coquille évalue et élargit ses arguments, il rencontre un -u
Set variable, qui a été spécifié par option de shell explicite pour provoquer une sortie immédiate de la coque actuelle et scriptée.
Et donc le [~ # ~] sortie [~ # ~] Piège, le cas échéant, est exécuté et que la coque sort avec un message de diagnostic et un statut de sortie autre que 0 - exactement comme il le devrait faire.
Quant au RC: 0 chose, je m'attends à ce qu'une version est une version spécifique de quelque sorte - probablement avec les deux déclencheurs pour le [~ # ~] # ~] se produisant en même temps et celui obtenant le code de sortie de l'autre (qui ne devrait pas se produire). Et de toute façon, avec un binaire à jour bash
comme installé par pacman
:
bash <<\IN
printf "Shell options:\t$-\n"
trap 'echo "EXIT (rc: $?)"' EXIT
set -eu
echo ${UNSET_VAR}
IN
J'ai ajouté la première ligne afin que vous puissiez voir que les conditions de la coque sont celles d'une coquille scriptée - c'est pas Interactive. La sortie est:
Shell options: hB
bash: line 4: UNSET_VAR: unbound variable
EXIT (rc: 1)
Voici quelques notes pertinentes de Changelogs récents :
$?
correctement.for
commandes pour avoir le mauvais numéro de ligne.trap
PABABLE DANS LES COMMANDES SUBSHELS ASYNCHRONES.trap
des gestionnaires pour ces signaux et permet la pluparttrap
gestionnaires à exécuter récursivement (en cours d'exécution trap
Les gestionnaires tandis qu'un gestionnaire trap
est en cours d'exécution).Je pense que c'est le dernier ou le premier qui est le plus pertinent - ou éventuellement une combinaison des deux. A trap
Handler est par sa nature asynchrone car tout son travail consiste à attendre et à gérer signaux asynchrones. Et vous déclenchez deux simultanément avec -eu
et $UNSET_VAR
.
Et alors peut-être que vous devriez simplement mettre à jour, mais si vous aimez vous-même, vous le ferez avec une coque différente.
(J'utilise Bash 4.2.53). Pour la partie 1, la page Bash Man dit simplement "Un message d'erreur sera écrit à l'erreur standard et une coque non interactive va sortir". Cela ne dit pas un piège à ERR sera appelé, même si je suis d'accord, il serait utile s'il le faisait.
Pour être pragmatique, si ce que vous voulez vraiment, c'est de faire facener plus proprement avec des variables non définies, une solution possible consiste à placer la majeure partie de votre code dans une fonction, puis à exécuter cette fonction dans une sous-coque et à récupérer le code de retour et la sortie STDERR. Voici un exemple où "cmd ()" est la fonction:
#!/bin/bash
trap 'rc=$?; echo "ERR at line ${LINENO} (rc: $rc)"; exit $rc' ERR
trap 'rc=$?; echo "EXIT (rc: $rc)"; exit $rc' EXIT
set -u
set -E # export trap to functions
cmd(){
echo "args=$*"
echo ${UNSET_VAR}
echo hello
}
oops(){
rc=$?
echo "$@"
return $rc # provoke ERR trap
}
exec 3>&1 # copy stdin to use in $()
if output=$(cmd "$@" 2>&1 >&3) # collect stderr, not stdout
then echo ok
else oops "fail: $output"
fi
Sur mon bash je reçois
./script my stuff; echo "exit was $?"
args=my stuff
fail: ./script: line 9: UNSET_VAR: unbound variable
ERR at line 15 (rc: 1)
EXIT (rc: 1)
exit was 1