Selon ce manuel de référence :
-E (également -o errtrace)
S'il est défini, tout piège sur ERR est hérité par les fonctions du shell, les substitutions de commandes et les commandes exécutées dans un environnement de sous-shell. Le
piège ERR n'est normalement pas hérité dans de tels cas.
Cependant, je dois mal l'interpréter, car ce qui suit ne fonctionne pas :
#!/usr/bin/env bash
# -*- bash -*-
set -e -o pipefail -o errtrace -o functrace
function boom {
echo "err status: $?"
exit $?
}
trap boom ERR
echo $( made up name )
echo " ! should not be reached ! "
Je connais déjà l'affectation simple, my_var=$(made_up_name)
, quittera le script avec set -e
(c'est-à-dire errexit).
Est-ce que -E/-o errtrace
censé fonctionner comme le code ci-dessus? Ou, très probablement, j'ai mal lu ?
Réponse acceptée :
Remarque :zsh
se plaindra de "mauvais modèles" si vous ne le configurez pas pour accepter les "commentaires en ligne" pour la plupart des exemples ici et ne les exécutez pas via un proxy shell comme je l'ai fait avec sh <<-CMD
.
Ok, donc, comme je l'ai dit dans les commentaires ci-dessus, je ne connais pas spécifiquement le set -E
de bash , mais je sais que les shells compatibles POSIX fournissent un moyen simple de tester une valeur si vous le souhaitez :
sh -evx <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works"
}
_test && echo "_test doesnt fail"
# END
CMD
sh: line 1: empty: error string
+ echo
+ echo 'echo still works'
echo still works
+ echo '_test doesnt fail'
_test doesnt fail
Ci-dessus, vous verrez que j'ai utilisé parameter expansion
pour tester ${empty?} _test()
toujours return
s une réussite - comme en témoigne le dernier echo
Cela se produit parce que la valeur d'échec tue le $( command substitution )
sous-shell qui le contient, mais son shell parent – _test
en ce moment - continue de camionner. Et echo
s'en fiche - c'est très heureux de ne servir qu'un newline; echo
n'est pas un essai.
Mais considérez ceci :
sh -evx <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
_test ||
echo "this doesnt even print"
# END
CMD
_test+ sh: line 1: empty: function doesnt run
Parce que j'ai nourri les _test()'s
entrée avec un paramètre pré-évalué dans le INIT here-document
maintenant le _test()
la fonction n'essaie même pas de s'exécuter du tout. De plus, le sh
shell abandonne apparemment complètement le fantôme et echo "this doesnt even print"
n'imprime même pas.
Ce n'est probablement pas ce que vous voulez.
Cela se produit parce que le ${var?}
style parameter-expansion est conçu pour quitter le shell
en cas de paramètre manquant, cela fonctionne comme ceci :
${parameter:?[word]}
Indiquer une erreur si
Null
ouUnset.
Si le paramètre n'est pas défini ou est nul, l'expansion of word
(ou un message indiquant qu'il n'est pas défini si le mot est omis) doit êtrewritten to standard error
et leshell exits with a non-zero exit status
. Sinon, la valeur du paramètreparameter shall be substituted
. Un shell interactif n'a pas besoin de se fermer.
Je ne vais pas copier/coller tout le document, mais si vous voulez un échec pour un set but null
valeur vous utilisez le formulaire :
${var
😕error message }
Avec le :colon
comme ci-dessus. Si vous voulez un null
valeur pour réussir, omettez simplement les deux-points. Vous pouvez également l'annuler et échouer uniquement pour les valeurs définies, comme je le montrerai dans un instant.
Une autre exécution de _test():
sh <<-CMD
_test() { echo $( ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?function doesnt run}
INIT
echo "this runs" |
( _test ; echo "this doesnt" ) ||
echo "now it prints"
# END
CMD
this runs
sh: line 1: empty: function doesnt run
now it prints
Cela fonctionne avec toutes sortes de tests rapides, mais ci-dessus, vous verrez que _test()
, exécuté à partir du milieu du pipeline
échoue, et en fait il contient command list
le sous-shell échoue complètement, car aucune des commandes de la fonction ne s'exécute ni le echo
suivant exécuter du tout, bien qu'il soit également démontré qu'il peut facilement être testé car echo "now it prints"
imprime maintenant.
Le diable est dans les détails, je suppose. Dans le cas ci-dessus, le shell qui sort n'est pas le _main | logic | pipeline
mais le ( subshell in which we ${test?} ) ||
donc un peu de bac à sable s'impose.
Et ce n'est peut-être pas évident, mais si vous vouliez passer uniquement pour le cas contraire, ou uniquement set=
valeurs, c'est assez simple aussi :
sh <<-CMD
N= #N is NULL
_test=$N #_test is also NULL and
v="something you would rather do without"
( #this subshell dies
echo "v is ${v+set}: and its value is ${v:+not NULL}"
echo "So this ${_test:-"$_test:="} will equal ${_test:="$v"}"
${_test:+${N:?so you test for it with a little nesting}}
echo "sure wish we could do some other things"
)
( #this subshell does some other things
unset v #to ensure it is definitely unset
echo "But here v is ${v-unset}: ${v:+you certainly wont see this}"
echo "So this ${_test:-"$_test:="} will equal NULL ${_test:="$v"}"
${_test:+${N:?is never substituted}}
echo "so now we can do some other things"
)
#and even though we set _test and unset v in the subshell
echo "_test is still ${_test:-"NULL"} and ${v:+"v is still $v"}"
# END
CMD
v is set: and its value is not NULL
So this $_test:= will equal something you would rather do without
sh: line 7: N: so you test for it with a little nesting
But here v is unset:
So this $_test:= will equal NULL
so now we can do some other things
_test is still NULL and v is still something you would rather do without
L'exemple ci-dessus tire parti des 4 formes de substitution de paramètres POSIX et de leurs différents :colon null
ou not null
essais. Il y a plus d'informations dans le lien ci-dessus, et c'est encore ici.
Et je suppose que nous devrions montrer notre _test
la fonction fonctionne aussi, n'est-ce pas ? Nous déclarons simplement empty=something
comme paramètre de notre fonction (ou à tout moment avant) :
sh <<-CMD
_test() { echo $( echo ${empty:?error string} ) &&
echo "echo still works" ; } 2<<-INIT
${empty?tested as a pass before function runs}
INIT
echo "this runs" >&2 |
( empty=not_empty _test ; echo "yay! I print now!" ) ||
echo "suspiciously quiet"
# END
CMD
this runs
not_empty
echo still works
yay! I print now!
Il convient de noter que cette évaluation est autonome - elle ne nécessite aucun test supplémentaire pour échouer. Quelques exemples supplémentaires :
sh <<-CMD
empty=
${empty?null, no colon, no failure}
unset empty
echo "${empty?this is stderr} this is not"
# END
CMD
sh: line 3: empty: this is stderr
sh <<-CMD
_input_fn() { set -- "[email protected]" #redundant
echo ${*?WHERES MY DATA?}
#echo is not necessary though
shift #sure hope we have more than $1 parameter
: ${*?WHERES MY DATA?} #: do nothing, gracefully
}
_input_fn heres some stuff
_input_fn one #here
# shell dies - third try doesnt run
_input_fn you there?
# END
CMD
heres some stuff
one
sh: line :5 *: WHERES MY DATA?
Et donc finalement nous revenons à la question initiale :comment gérer les erreurs dans un $(command substitution)
sous-shell ? La vérité est qu'il y a deux façons, mais aucune n'est directe. Le cœur du problème est le processus d'évaluation du shell - les extensions du shell (y compris $(command substitution)
) se produisent plus tôt dans le processus d'évaluation du shell que l'exécution actuelle de la commande shell, c'est-à-dire lorsque vos erreurs peuvent être détectées et piégées.
Le problème rencontré par l'op est qu'au moment où le shell actuel évalue les erreurs, le $(command substitution)
le sous-shell a déjà été remplacé - il ne reste aucune erreur.
Quelles sont donc les deux voies ? Soit vous le faites explicitement dans le $(command substitution)
sous-shell avec des tests comme vous le feriez sans lui, ou vous absorbez ses résultats dans une variable shell actuelle et testez sa valeur.
Méthode 1 :
echo "$(madeup && echo : || echo '${fail:?die}')" |
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Méthode 2 :
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
Cela
échouera quel que soit le nombre de variables déclarées par ligne :
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
Et notre valeur de retour reste constante :
echo $?
1
MAINTENANT LE PIÈGE :
trap 'printf %s\n trap resurrects shell!' ERR
v1="$(madeup)" v2="$(printf %s\n shown after trap)"
echo "${v1:?#1 - still stderr}" "${v2:?invisible}"
sh: command not found: madeup
sh: v1: #1 - still stderr
trap
resurrects
shell!
shown
after
trap
echo $?
0