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 shell proxy 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 les bashset -E
, 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
Au- dessus , vous verrez que si je parameter expansion
tester ${empty?} _test()
encore return
est un passe - comme il est témoigna dans le dernier echo
Cela se produit parce que la valeur a échoué tue le $( command substitution )
sous - shell qui le contient, mais sa coquille mère - _test
à ce moment - garde sur le camionnage. Etecho
peu importe - il est très heureux de servir seulement un test \newline; echo
n'est pas .
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 alimenté l' _test()'s
entrée avec un paramètre pré-évalué dans le INIT here-document
maintenant, la _test()
fonction n'essaie même pas de fonctionner du tout. De plus, le sh
shell abandonne apparemment le fantôme et echo "this doesnt even print"
ne s'imprime même pas.
Ce n'est probablement pas ce que vous voulez.
Cela se produit car l' ${var?}
expansion des paramètres de style est conçue pour quitter leshell
en cas de paramètre manquant, cela fonctionne comme ceci :
${parameter:?[word]}
Indique une erreur si Null
ou Unset.
si le paramètre est non défini ou nul, le expansion of word
(ou un message indiquant qu'il est non défini si le mot est omis) doit être written to standard error
et le shell exits with a non-zero exit status
. Sinon, la valeur deparameter shall be substituted
. Un shell interactif n'a pas besoin de quitter.
Je ne vais pas copier / coller le document entier, 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 qu'une null
valeur réussisse, 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 série 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 des pipeline
échecs, et en fait son command list
sous-shell contenant échoue complètement, car aucune des commandes de la fonction ne s'exécute ni la suivante echo
, bien qu'il soit également démontré qu'il peut facilement être testé car echo "now it prints"
il s'imprime maintenant.
Le diable est dans les détails, je suppose. Dans le cas ci-dessus, le shell qui sort n'est pas celui du script _main | logic | pipeline
mais le( subshell in which we ${test?} ) ||
un petit bac à sable est donc nécessaire.
Et ce n'est peut-être pas évident, mais si vous vouliez passer uniquement pour le cas contraire, ou uniquement pour les set=
valeurs, c'est aussi assez simple:
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 diverses :colon null
ounot null
des tests. Il y a plus d'informations dans le lien ci-dessus, et le voici à nouveau .
Et je suppose que nous devrions aussi montrer notre _test
travail fonctionnel, non? Nous déclarons simplementempty=something
comme paramètre de notre fonction (ou à tout moment à l'avance):
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 -- "$@" #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 enfin, nous revenons à la question initiale: comment gérer les erreurs dans un $(command substitution)
sous-shell? La vérité est - 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 des commandes du shell - c'est à ce moment que 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)
sous shell a déjà été remplacé - aucune erreur ne subsiste.
Alors, quelles sont les deux façons? Soit vous le faites explicitement dans le$(command substitution)
sous shell avec des tests comme vous le feriez sans, soit 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
echo $( made up name )
par$( made up name )
produit le comportement souhaité. Je n'ai pas d'explication cependant.