Algumas vezes, quando li sobre programação, me deparei com o conceito de "retorno de chamada".
O engraçado é que nunca encontrei uma explicação que possa chamar de "didática" ou "clara" para esse termo "função de retorno de chamada" (quase todas as explicações que li me pareceram bastante diferentes das outras e me senti confuso).
O conceito de "retorno de chamada" da programação existe no Bash? Nesse caso, responda com um exemplo pequeno e simples do Bash.
declarative.bash
interessante, como uma estrutura que explora explicitamente as funções configuradas para serem invocadas quando um determinado valor é necessário.Respostas:
Na programação imperativa típica , você escreve sequências de instruções e elas são executadas uma após a outra, com fluxo de controle explícito. Por exemplo:
etc.
Como pode ser visto no exemplo, na programação imperativa, você segue o fluxo de execução com bastante facilidade, sempre subindo a partir de qualquer linha de código para determinar seu contexto de execução, sabendo que todas as instruções fornecidas serão executadas como resultado de sua execução. local no fluxo (ou nos locais dos sites de chamadas, se você estiver escrevendo funções).
Como os retornos de chamada alteram o fluxo
Ao usar retornos de chamada, em vez de colocar o uso de um conjunto de instruções "geograficamente", você descreve quando deve ser chamado. Exemplos típicos em outros ambientes de programação são casos como “baixar este recurso e, quando o download estiver concluído, chame esse retorno de chamada”. O Bash não possui uma construção genérica de retorno de chamada desse tipo, mas possui retornos de chamada, para tratamento de erros e algumas outras situações; por exemplo (é preciso primeiro entender os modos de substituição de comando e saída do Bash para entender esse exemplo):
Se você quiser tentar fazer isso sozinho, salve o acima em um arquivo, por exemplo
cleanUpOnExit.sh
, torne-o executável e execute-o:Meu código aqui nunca chama explicitamente a
cleanup
função; ele diz ao Bash quando chamá-lo, usandotrap cleanup EXIT
, ou seja , “caro Bash, execute ocleanup
comando quando você sair” (ecleanup
é uma função que eu defini anteriormente, mas pode ser qualquer coisa que o Bash entenda). O Bash suporta isso para todos os sinais não fatais, saídas, falhas de comando e depuração geral (você pode especificar um retorno de chamada que é executado antes de cada comando). O retorno de chamada aqui é acleanup
função, que é "chamada de volta" por Bash imediatamente antes da saída do shell.Você pode usar a capacidade do Bash para avaliar os parâmetros do shell como comandos, para criar uma estrutura orientada a retorno de chamada; isso está um pouco além do escopo desta resposta e talvez causasse mais confusão ao sugerir que a passagem de funções sempre envolva retornos de chamada. Consulte Bash: passe uma função como parâmetro para alguns exemplos da funcionalidade subjacente. A idéia aqui, como nos retornos de chamada de manipulação de eventos, é que as funções podem receber dados como parâmetros, mas também outras funções - isso permite que os chamadores forneçam comportamento e dados. Um exemplo simples dessa abordagem pode parecer
(Eu sei que isso é um pouco inútil, pois
cp
pode lidar com vários arquivos, é apenas para ilustração.)Aqui criamos uma função,
doonall
que pega outro comando, dado como parâmetro, e a aplica ao restante de seus parâmetros; então usamos isso para chamar abackup
função em todos os parâmetros fornecidos ao script. O resultado é um script que copia todos os seus argumentos, um por um, para um diretório de backup.Esse tipo de abordagem permite que as funções sejam escritas com responsabilidades únicas:
doonall
a responsabilidade é executar algo em todos os seus argumentos, um de cada vez;backup
A responsabilidade de fazer uma cópia do seu argumento (único) em um diretório de backup. Ambosdoonall
ebackup
podem ser usados em outros contextos, o que permite mais reutilização de código, melhores testes etc.Nesse caso, o retorno de chamada é a
backup
função, que dizemosdoonall
para "retornar" em cada um de seus outros argumentos - fornecemos tanto odoonall
comportamento (seu primeiro argumento) quanto os dados (os argumentos restantes).(Observe que, no tipo de caso de uso demonstrado no segundo exemplo, eu não usaria o termo "retorno de chamada", mas talvez seja um hábito resultante dos idiomas que eu uso. Penso nisso como passando funções ou lambdas ao redor , em vez de registrar retornos de chamada em um sistema orientado a eventos.)
fonte
Primeiro, é importante observar que o que faz de uma função uma função de retorno de chamada é como ela é usada, não o que faz. Um retorno de chamada é quando o código que você escreve é chamado a partir do código que você não escreveu. Você está pedindo ao sistema para ligar de volta quando algum evento específico acontecer.
Um exemplo de retorno de chamada na programação de shell é traps. Uma interceptação é um retorno de chamada que não é expresso como uma função, mas como um pedaço de código para avaliar. Você está solicitando que o shell chame seu código quando o shell recebe um sinal específico.
Outro exemplo de retorno de chamada é a
-exec
ação dofind
comando. O trabalho dofind
comando é percorrer diretórios recursivamente e processar cada arquivo por vez. Por padrão, o processamento é para imprimir o nome do arquivo (implícito-print
), mas com-exec
o processamento é para executar um comando que você especificar. Isso se encaixa na definição de um retorno de chamada, embora, no início, não seja muito flexível, pois o retorno de chamada é executado em um processo separado.Se você implementou uma função de localização, você pode usar uma função de retorno de chamada para chamar cada arquivo. Aqui está uma função de localização ultra simplificada que pega um nome de função (ou nome de comando externo) como argumento e a chama em todos os arquivos regulares no diretório atual e em seus subdiretórios. A função é usada como um retorno de chamada que é chamado toda vez que
call_on_regular_files
encontra um arquivo regular.Os retornos de chamada não são tão comuns na programação de shell quanto em outros ambientes, porque os shells são projetados principalmente para programas simples. Os retornos de chamada são mais comuns em ambientes em que os dados e o fluxo de controle têm maior probabilidade de alternar entre partes do código que são gravadas e distribuídas independentemente: o sistema base, várias bibliotecas, o código do aplicativo.
fonte
foreach_server() { declare callback="$1"; declare server; for server in 192.168.0.1 192.168.0.2 192.168.0.3; do "$callback" "$server"; done; }
que você poderia funcionar comoforeach_server echo
,foreach_server nslookup
, etc. Odeclare callback="$1"
é tão simples como ele pode ficar no entanto: a chamada de retorno tem de ser aprovada em algum lugar, ou não é um retorno de chamada."retornos de chamada" são apenas funções passadas como argumentos para outras funções.
No nível do shell, isso significa simplesmente scripts / funções / comandos passados como argumentos para outros scripts / funções / comandos.
Agora, para um exemplo simples, considere o seguinte script:
tendo a sinopse
será aplicado
filter
a cadafile
argumento e, em seguida, chamecommand
com os resultados dos filtros como argumentos.Por exemplo:
Isso é muito próximo ao que você pode fazer no lisp (apenas brincando ;-))
Algumas pessoas insistem em limitar o termo "retorno de chamada" a "manipulador de eventos" e / ou "encerramento" (função + tupla de dados / ambiente); este não é de forma alguma o significado geralmente aceito . E uma razão pela qual "retornos de chamada" nesses sentidos restritos não são de muita utilidade no shell é porque os recursos de programação dinâmica pipes + paralelismo + programação são muito mais poderosos e você já está pagando por eles em termos de desempenho, mesmo se tente usar o shell como uma versão desajeitada de
perl
oupython
.fonte
%
interpolação em filtros, a coisa toda poderia ser reduzido para:cmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")
. Mas isso é muito menos útil e ilustrativo.$1 <($2 "$3") <($2 "$4")
Mais ou menos.
Uma maneira simples de implementar um retorno de chamada no bash é aceitar o nome de um programa como parâmetro, que atua como "função de retorno de chamada".
Isso seria usado assim:
Claro que você não tem fechamentos no bash. Portanto, a função de retorno de chamada não tem acesso às variáveis no lado do chamador. No entanto, você pode armazenar dados que o retorno de chamada precisa em variáveis de ambiente. Retornar informações do retorno de chamada para o script do invocador é mais complicado. Os dados podem ser colocados em um arquivo.
Se o seu design permitir que tudo seja tratado em um único processo, você poderá usar uma função shell para o retorno de chamada e, nesse caso, a função de retorno de chamada obviamente terá acesso às variáveis no lado do invocador.
fonte
Apenas para adicionar algumas palavras às outras respostas. O retorno de chamada da função opera em funções externas à função que chama de volta. Para que isso seja possível, toda uma definição da função a ser chamada de volta precisa ser passada para a função que chama de volta, ou seu código deve estar disponível para a função que está chamando de volta.
O primeiro (passar código para outra função) é possível, embora eu pule um exemplo, pois isso envolveria complexidade. A última (passar a função pelo nome) é uma prática comum, pois as variáveis e funções declaradas fora do escopo de uma função estão disponíveis nessa função, desde que sua definição anteceda a chamada à função que opera nelas (que, por sua vez, , a ser declarado antes de ser chamado).
Observe também que algo semelhante acontece quando as funções são exportadas. Um shell que importa uma função pode ter uma estrutura pronta e aguardar pelas definições de função para colocá-las em ação. A exportação de funções está presente no Bash e causou problemas anteriormente sérios, btw (que foi chamado de Shellshock):
Concluirei esta resposta com mais um método de passar uma função para outra, que não está explicitamente presente no Bash. Este está passando por endereço, não por nome. Isso pode ser encontrado no Perl, por exemplo. O Bash oferece desta maneira nem para funções nem variáveis. Mas se, como você afirma, deseja ter uma imagem mais ampla do Bash como apenas um exemplo, você deve saber que o código de função pode residir em algum lugar da memória e esse código pode ser acessado por esse local de memória, que é chamou seu endereço.
fonte
Um dos exemplos mais simples de retorno de chamada no bash é com o qual muitas pessoas estão familiarizadas, mas não percebem qual padrão de design estão realmente usando:
cron
Cron permite que você especifique um executável (um binário ou script) que o programa cron retornará quando algumas condições forem atendidas (a especificação de tempo)
Digamos que você tenha um script chamado
doEveryDay.sh
. A maneira sem retorno de chamada para escrever o script é:A forma de retorno de chamada para escrever é simplesmente:
Então, no crontab, você definiria algo como
Você não precisaria escrever o código para aguardar o evento ser acionado, mas confiar
cron
para chamar seu código de volta.Agora, considere COMO você escreveria esse código no bash.
Como você executaria outro script / função no bash?
Vamos escrever uma função:
Agora você criou uma função que aceita um retorno de chamada. Você pode simplesmente chamar assim:
Obviamente, a função every24hours nunca retorna. O Bash é um pouco único, pois podemos torná-lo assíncrono com muita facilidade e gerar um processo anexando
&
:Se você não quiser isso como uma função, faça isso como um script:
Como você pode ver, os retornos de chamada no bash são triviais. É simplesmente:
E chamar o retorno de chamada é simplesmente:
Como você pode ver no formulário acima, os retornos de chamada raramente são recursos diretamente dos idiomas. Eles geralmente estão programando de maneira criativa usando os recursos de linguagem existentes. Qualquer idioma que possa armazenar um ponteiro / referência / cópia de algum bloco / função / script de código pode implementar retornos de chamada.
fonte
watch
efind
(quando utilizado com-exec
parâmetro)Um retorno de chamada é uma função chamada quando algum evento ocorre. Com
bash
, o único mecanismo de manipulação de eventos está relacionado a sinais, a saída do shell e os eventos de erros do shell, eventos de depuração e scripts de função / origem retornam eventos.Aqui está um exemplo de um retorno de chamada inútil, mas simples, aproveitando os traps de sinal.
Primeiro, crie o script implementando o retorno de chamada:
Em seguida, execute o script em um terminal:
$ ./callback-example
e em outro, envie o
USR1
sinal para o processo shell.Cada sinal enviado deve acionar a exibição de linhas como estas no primeiro terminal:
ksh93
, como o shell que implementa muitos recursosbash
adotados posteriormente, fornece o que chama de "funções de disciplina". Essas funções, não disponíveis combash
, são chamadas quando uma variável do shell é modificada ou referenciada (isto é, lida). Isso abre caminho para aplicativos mais direcionados a eventos.Por exemplo, esse recurso permitiu que retornos de chamada no estilo X11 / Xt / Motif em widgets gráficos fossem implementados em uma versão antiga das
ksh
extensões gráficas incluídas chamadasdtksh
. Veja o manual do dksh .fonte