Nota: zsh
se quejará de "patrones incorrectos" si no lo configura para aceptar "comentarios en línea" para la mayoría de los ejemplos aquí y no los ejecuta a través de un shell proxy como lo he hecho sh <<-\CMD
.
De acuerdo, como dije en los comentarios anteriores, no sé específicamente sobre bashset -E
, pero sé que los shells compatibles con POSIX proporcionan un medio simple de probar un valor si lo desea:
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
Arriba verá que, aunque solía parameter expansion
probar, ${empty?} _test()
todavía return
es un paso, como se evidencia en el último. echo
Esto ocurre porque el valor fallido mata la $( command substitution )
subshell que lo contiene, pero su shell principal, _test
en este momento, continúa transportándose en camiones. Y echo
no le importa, está muy feliz de servir solo y no\newline; echo
es una prueba.
Pero considere esto:
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
Debido a que introduje la _test()'s
entrada con un parámetro previamente evaluado en el INIT here-document
ahora, la _test()
función ni siquiera intenta ejecutarse en absoluto. Además, el sh
caparazón aparentemente abandona el fantasma por completo y echo "this doesnt even print"
ni siquiera imprime.
Probablemente eso no sea lo que quieres.
Esto sucede porque el ${var?}
estilo de expansión de parámetros está diseñado para salirshell
en caso de que falte un parámetro, funciona así :
${parameter:?[word]}
Indique Error si Null
o Unset.
si el parámetro no está establecido o es nulo, el expansion of word
(o un mensaje que indica que no está establecido si se omite la palabra) será written to standard error
y el shell exits with a non-zero exit status
. De lo contrario, el valor de parameter shall be substituted
. Un shell interactivo no necesita salir.
No copiaré / pegaré todo el documento, pero si desea una falla para un set but null
valor, use el formulario:
${var
:? error message }
Con lo de :colon
arriba. Si desea que un null
valor tenga éxito, simplemente omita los dos puntos. También puede negarlo y fallar solo para los valores establecidos, como lo mostraré en un momento.
Otra carrera 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
Esto funciona con todo tipo de pruebas rápidas, pero arriba verá que se _test()
ejecuta desde la mitad de los pipeline
errores y, de hecho, su command list
subshell que falla falla por completo, ya que ninguno de los comandos dentro de la función se ejecuta ni la siguiente echo
ejecución, aunque también se muestra que puede probarse fácilmente porque echo "now it prints"
ahora imprime.
El diablo está en los detalles, supongo. En el caso anterior, el shell que sale no es el script _main | logic | pipeline
sino ( subshell in which we ${test?} ) ||
que se requiere un pequeño sandboxing.
Y puede que no sea obvio, pero si solo desea pasar por el caso opuesto, o solo los set=
valores, también es bastante 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
El ejemplo anterior aprovecha las 4 formas de sustitución de parámetros POSIX y sus diversas :colon null
o not null
pruebas. Hay más información en el enlace de arriba, y aquí está de nuevo .
Y supongo que también deberíamos mostrar el _test
funcionamiento de nuestra función, ¿verdad? Simplemente declaramos empty=something
como parámetro a nuestra función (o en cualquier momento de antemano):
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!
Cabe señalar que esta evaluación es independiente: no requiere ninguna prueba adicional para fallar. Un par de ejemplos más:
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?
Y finalmente volvemos a la pregunta original: ¿cómo manejar los errores en una $(command substitution)
subshell? La verdad es que hay dos formas, pero ninguna es directa. El núcleo del problema es el proceso de evaluación del shell: las expansiones del shell (incluido $(command substitution)
) ocurren antes en el proceso de evaluación del shell que la ejecución actual del comando del shell, que es cuando sus errores pueden ser atrapados y atrapados.
El problema que experimenta el operador operativo es que cuando el shell actual evalúa los errores, el $(command substitution)
subshell ya se ha sustituido, no quedan errores.
¿Cuáles son las dos formas? O lo hace explícitamente dentro del $(command substitution)
subshell con pruebas como lo haría sin él, o absorbe sus resultados en una variable de shell actual y prueba su valor.
Método 1:
echo "$(madeup && echo \: || echo '${fail:?die}')" |\
. /dev/stdin
sh: command not found: madeup
/dev/stdin:1: fail: die
echo $?
126
Método 2:
var="$(madeup)" ; echo "${var:?die} still not stderr"
sh: command not found: madeup
sh: var: die
echo $?
1
Esto fallará independientemente del número de variables declaradas por línea:
v1="$(madeup)" v2="$(ls)" ; echo "${v1:?}" "${v2:?}"
sh: command not found: madeup
sh: v1: parameter not set
Y nuestro valor de retorno permanece constante:
echo $?
1
AHORA LA TRAMPA:
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 )
con$( made up name )
produce el comportamiento deseado. Sin embargo, no tengo una explicación.