Eu já discuti como e por que os métodos abaixo funcionam em várias ocasiões antes, então não farei isso novamente. Pessoalmente, os meus favoritos sobre o tema são aqui e aqui .
Se você não estiver interessado em ler isso, mas ainda assim curioso, apenas entenda que os aqui-docs anexados à entrada da função são avaliados para a expansão do shell anterior a função é executada e gerada novamente no estado em que estavam quando a função foi definida a cada hora em que a função é chamada.
DECLARAR
Você só precisa de uma função que declare outras funções.
_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
RODA TI
Aqui eu chamo _fn_init
para me declarar uma função chamada 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
REQUERIDO
Se eu quiser chamar essa função, ela morrerá, a menos que a variável de ambiente _if_unset
esteja definida.
fn
#OUTPUT#
+ fn
/dev/fd/4: line 1: _if_unset: shell will ERR and print this to stderr
Por favor, note a ordem dos rastreios do shell - não só o fn
falha quando chamado quando _if_unset
não está definido, mas nunca é executado em primeiro lugar . Esse é o fator mais importante a ser entendido ao trabalhar com expansões de documentos aqui - elas sempre devem ocorrer primeiro, porque elas são <<input
, afinal.
O erro vem de /dev/fd/4
porque o shell pai está avaliando essa entrada antes de entregá-la à função. É a maneira mais simples e eficiente de testar o ambiente necessário.
De qualquer forma, a falha é facilmente corrigida.
_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
FLEXÍVEL
A variável common_param
é avaliada como um valor padrão na entrada para cada função declarada por _fn_init
. Mas esse valor também é mutável para qualquer outro, que também será honrado por todas as funções igualmente declaradas. Vou deixar os rastros do shell agora - não estamos entrando em território desconhecido aqui ou algo assim.
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 ;
Acima, declaro duas funções e defino _if_unset
. Agora, antes de chamar qualquer uma das funções, eu retiro common_param
para que você possa ver que elas serão definidas quando eu as chamar.
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
E agora, do escopo do chamador:
echo $common_param
#OUTPUT#
REQ/RESET added to all funcs
Mas agora quero que seja algo totalmente diferente:
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.
E se eu desativar o _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
RESET
Se você precisar redefinir o estado da função a qualquer momento, isso será feito facilmente. Você só precisa fazer (de dentro da função):
. /dev/fd/5
Salvei os argumentos usados para declarar inicialmente a função no descritor de arquivo 5<<\RESET
input. Portanto, .dot
sourcing que no shell a qualquer momento irá repetir o processo que o configurou em primeiro lugar. É tudo muito fácil, realmente e praticamente totalmente portátil se você estiver disposto a ignorar o fato de que POSIX não especifica realmente os caminhos do nó do dispositivo do descritor de arquivo (que são uma necessidade para o .dot
do shell).
Você pode expandir facilmente esse comportamento e configurar estados diferentes para sua função.
MAIS?
Isso apenas arranha a superfície, a propósito. Costumo usar essas técnicas para incorporar pequenas funções auxiliares declaráveis a qualquer momento na entrada de uma função principal - por exemplo, para matrizes posicionais $@
adicionais, conforme necessário. De fato - como eu acredito, deve ser algo muito próximo disso que os shells de maior ordem fazem de qualquer maneira. Você pode ver que eles são facilmente chamados de forma programática.
Eu também gostaria de declarar uma função geradora que aceita um tipo limitado de parâmetro e então define uma função de queimador de uso único ou de escopo limitado ao longo das linhas de uma função lambda - ou uma função em linha - que simplesmenteunset -f
quando se passa. Você pode passar uma função de shell por aí.