Ya he discutido los cómo y los porqués de la forma en que funcionan los métodos a continuación en varias ocasiones antes, así que no lo volveré a hacer. Personalmente, mis favoritos sobre el tema están aquí y aquí .
Si no está interesado en leer eso, pero todavía tiene curiosidad, comprenda que los documentos adjuntos a la entrada de la función se evalúan para la expansión del shell antes de que se ejecute la función, y que se generan nuevamente en el estado en que estaban cuando se definió la función cada vez que se llama a la función.
DECLARAR
Solo necesita una función que declare otras funciones.
_fn_init() { . /dev/fd/4 ; } 4<<INIT
${1}() { $(shift ; printf %s\\n "$@")
} 4<<-REQ 5<<-\\RESET
: \${_if_unset?shell will ERR and print this to stderr}
: \${common_param="REQ/RESET added to all funcs"}
REQ
_fn_init $(printf "'%s' " "$@")
RESET
INIT
EJECUTARLO
Aquí llamo _fn_init
para declararme una función llamada fn
.
set -vx
_fn_init fn \
'echo "this would be command 1"' \
'echo "$common_param"'
#OUTPUT#
+ _fn_init fn 'echo "this would be command 1"' 'echo "$common_param"'
shift ; printf %s\\n "$@"
++ shift
++ printf '%s\n' 'echo "this would be command 1"' 'echo "$common_param"'
printf "'%s' " "$@"
++ printf ''\''%s'\'' ' fn 'echo "this would be command 1"' 'echo "$common_param"'
#ALL OF THE ABOVE OCCURS BEFORE _fn_init RUNS#
#FIRST AND ONLY COMMAND ACTUALLY IN FUNCTION BODY BELOW#
+ . /dev/fd/4
#fn AFTER _fn_init .dot SOURCES IT#
fn() { echo "this would be command 1"
echo "$common_param"
} 4<<-REQ 5<<-\RESET
: ${_if_unset?shell will ERR and print this to stderr}
: ${common_param="REQ/RESET added to all funcs"}
REQ
_fn_init 'fn' \
'echo "this would be command 1"' \
'echo "$common_param"'
RESET
NECESARIO
Si quiero llamar a esta función, morirá a menos que se establezca la variable de entorno _if_unset
.
fn
#OUTPUT#
+ fn
/dev/fd/4: line 1: _if_unset: shell will ERR and print this to stderr
Tenga en cuenta el orden de los rastreos de shell: no solo fn
falla cuando se llama cuando no _if_unset
está configurado, sino que nunca se ejecuta en primer lugar . Este es el factor más importante para entender cuando se trabaja con expansiones de documentos aquí: siempre deben ocurrir primero porque <<input
después de todo lo son .
El error proviene de /dev/fd/4
que el shell principal está evaluando esa entrada antes de pasarla a la función. Es la forma más simple y eficiente de probar el entorno requerido.
De todos modos, la falla se remedia fácilmente.
_if_unset=set fn
#OUTPUT#
+ _if_unset=set
+ fn
+ echo 'this would be command 1'
this would be command 1
+ echo 'REQ/RESET added to all funcs'
REQ/RESET added to all funcs
FLEXIBLE
La variable common_param
se evalúa a un valor predeterminado en la entrada para cada función declarada por _fn_init
. Pero ese valor también se puede cambiar a cualquier otro que también sea honrado por cada función declarada de manera similar. Ahora dejaré los rastros de la cáscara: no vamos a entrar en ningún territorio desconocido aquí ni nada.
set +vx
_fn_init 'fn' \
'echo "Hi! I am the first function."' \
'echo "$common_param"'
_fn_init 'fn2' \
'echo "This is another function."' \
'echo "$common_param"'
_if_unset=set ;
Arriba declaro dos funciones y conjunto _if_unset
. Ahora, antes de llamar a cualquiera de las funciones, lo desarmaré common_param
para que pueda ver que lo configurarán ellos mismos cuando los llame.
unset common_param ; echo
fn ; echo
fn2 ; echo
#OUTPUT#
Hi! I am the first function.
REQ/RESET added to all funcs
This is another function.
REQ/RESET added to all funcs
Y ahora desde el alcance de la persona que llama:
echo $common_param
#OUTPUT#
REQ/RESET added to all funcs
Pero ahora quiero que sea algo completamente distinto:
common_param="Our common parameter is now something else entirely."
fn ; echo
fn2 ; echo
#OUTPUT#
Hi! I am the first function.
Our common parameter is now something else entirely.
This is another function.
Our common parameter is now something else entirely.
¿Y si me desarmo _if_unset
?
unset _if_unset ; echo
echo "fn:"
fn ; echo
echo "fn2:"
fn2 ; echo
#OUTPUT#
fn:
dash: 1: _if_unset: shell will ERR and print this to stderr
fn2:
dash: 1: _if_unset: shell will ERR and print this to stderr
REINICIAR
Si necesita restablecer el estado de la función en cualquier momento, puede hacerlo fácilmente. Solo necesita hacer (desde dentro de la función):
. /dev/fd/5
Guardé los argumentos utilizados para declarar inicialmente la función en el 5<<\RESET
descriptor de archivo de entrada. Por lo tanto, la .dot
fuente en el shell en cualquier momento repetirá el proceso que lo configuró en primer lugar. Todo es bastante fácil, realmente y prácticamente totalmente portátil si está dispuesto a pasar por alto el hecho de que POSIX no especifica realmente las rutas de nodo del dispositivo descriptor de archivos (que son una necesidad para el shell .dot
).
Podría expandir fácilmente este comportamiento y configurar diferentes estados para su función.
¿MÁS?
Esto apenas rasca la superficie, por cierto. A menudo uso estas técnicas para incrustar pequeñas funciones auxiliares declarables en cualquier momento en la entrada de una función principal, por ejemplo, para $@
matrices posicionales adicionales según sea necesario. De hecho, como creo, debe ser algo muy parecido a esto que los proyectiles de orden superior hacen de todos modos. Puedes ver que son muy fáciles de nombrar mediante programación.
También me gusta declarar una función generadora que acepte un tipo limitado de parámetro y luego defina una función de quemador de un solo uso o limitada por el alcance a lo largo de las líneas de una lambda, o una función en línea, que simplemente unset -f
es en sí misma cuando mediante. Puede pasar una función de shell.
typeset
necesario? ¿No lo declararía de otra manera?