Algumas vezes, quando li sobre programação, me deparei com o conceito de "retorno de chamada".
Curiosamente, nunca encontrei uma explicação que eu possa chamar de "didática" ou "clara" para esse termo "função de retorno de chamada" (quase qualquer explicação que li me pareceu bastante diferente de outra e me senti confuso).
O conceito de "retorno de chamada" de programação existe no Bash? Em caso afirmativo, responda com um pequeno e simples exemplo Bash.
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 trabalhando a partir de qualquer linha de código para determinar seu contexto de execução, sabendo que quaisquer instruções que você der serão executadas como resultado de sua localização no fluxo (ou locais de seus sites de chamada, se você estiver escrevendo funções).
Como os retornos de chamada alteram o fluxo
Quando você usa callbacks, ao invés de colocar o uso de um conjunto de instruções “geograficamente”, você descreve quando ele 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, chamar este 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 a substituição de comandos e os modos de saída do Bash para entender esse exemplo):
Se você quiser experimentar isso sozinho, salve o acima em um arquivo, digamos
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 , “querido Bash, por favor, execute ocleanup
comando quando você sair” (ecleanup
acontece de ser 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” pelo Bash logo antes do shell sair.Você pode usar a habilidade do Bash para avaliar parâmetros de shell como comandos, para construir uma estrutura orientada a retorno de chamada; isso está um pouco além do escopo desta resposta e talvez cause mais confusão ao sugerir que passar funções sempre envolve retornos de chamada. Consulte Bash: passe uma função como parâmetro para alguns exemplos da funcionalidade subjacente. A ideia aqui, como nos callbacks 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 ser
(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 recebe outro comando, dado como parâmetro, e o aplica aos demais 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 funções sejam escritas com responsabilidades únicas: a responsabilidade
doonall
de ' é executar algo em todos os seus argumentos, um de cada vez;backup
A responsabilidade de é fazer uma cópia de seu (único) argumento 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 “chamar de volta” em cada um de seus outros argumentos — fornecemosdoonall
comportamento (seu primeiro argumento) e 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 das linguagens que uso. Penso nisso como passar funções ou lambdas ao redor , em vez de registrar retornos de chamada em um sistema orientado a eventos.)
Primeiro é importante notar que o que torna uma função uma função de retorno de chamada é como ela é usada, não o que ela faz. Um retorno de chamada é quando o código que você escreve é chamado de um 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 shell são as armadilhas. Uma armadilha é um retorno de chamada que não é expresso como uma função, mas como um pedaço de código a ser avaliado. Você está pedindo ao shell para chamar 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 os diretórios recursivamente e processar cada arquivo por vez. Por padrão, o processamento é imprimir o nome do arquivo (implícito-print
), mas com-exec
o processamento é executar um comando que você especificar. Isso se encaixa na definição de um retorno de chamada, embora não seja muito flexível, pois o retorno de chamada é executado em um processo separado.Se você implementou uma função do tipo find, você pode fazer com que ela use uma função de retorno de chamada para chamar cada arquivo. Aqui está uma função tipo find ultra-simplificada que recebe um nome de função (ou nome de comando externo) como argumento e o chama em todos os arquivos regulares no diretório atual e seus subdiretórios. A função é usada como um retorno de chamada que é chamado toda vez
call_on_regular_files
que encontra um arquivo regular.Os retornos de chamada não são tão comuns na programação de shell como em alguns outros ambientes porque os shells são projetados principalmente para programas simples. Os retornos de chamada são mais comuns em ambientes em que o fluxo de dados e controle é mais propenso a alternar entre partes do código que são escritas e distribuídas independentemente: o sistema básico, várias bibliotecas, o código do aplicativo.
"callbacks" 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, chamarácommand
com as saídas dos filtros como argumentos.Por exemplo:
Isso é muito próximo do que você pode fazer em lisp (brincadeira ;-))
Algumas pessoas insistem em limitar o termo "callback" para "manipulador de eventos" e/ou "fechamento" (função + tupla de dados/ambiente); este não é de forma alguma o significado geralmente aceito . E uma razão pela qual "callbacks" nesses sentidos estreitos não são muito úteis no shell é porque pipes + paralelismo + recursos de programação dinâmica são muito mais poderosos, e você já está pagando por eles em termos de desempenho, mesmo se você tente usar o shell como uma versão desajeitada de
perl
oupython
.Tipo de.
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 encerramentos no bash. Portanto, a função de retorno de chamada não tem acesso às variáveis do lado do chamador. Você pode, no entanto, armazenar dados que o retorno de chamada precisa em variáveis de ambiente. Passar 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 permite que tudo seja tratado em um único processo, você pode usar uma função shell para o retorno de chamada e, nesse caso, a função de retorno de chamada tem, é claro, acesso às variáveis no lado do invocador.
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, uma definição completa da função a ser chamada de volta precisa ser passada para a função que está chamando 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. O último (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 para a função que opera nelas (que, por sua vez, , para 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 estar apenas esperando as definições de função para colocá-las em ação. A exportação da função está presente no Bash e causou problemas anteriormente sérios, btw (que se chamava Shellshock):
Vou completar esta resposta com mais um método de passar uma função para outra função, que não está explicitamente presente no Bash. Este está passando por endereço, não por nome. Isso pode ser encontrado em Perl, por exemplo. O Bash não oferece esse caminho nem para funções nem para variáveis. Mas se, como você afirma, você deseja ter uma imagem mais ampla com Bash como apenas um exemplo, então você deve saber que o código da 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.
Um dos exemplos mais simples de retorno de chamada no bash é aquele com o qual muitas pessoas estão familiarizadas, mas não percebem qual padrão de design elas estão realmente usando:
cron
Cron permite que você especifique um executável (um binário ou script) que o programa cron chamará de volta 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 de escrever o script é:A maneira de retorno de chamada para escrevê-lo é simplesmente:
Então no crontab você definiria algo como
Você não precisaria escrever o código para esperar que o evento fosse acionado, mas, em vez disso, confiaria em
cron
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:
Claro, a função every24hours nunca retorna. Bash é um pouco único, pois podemos facilmente torná-lo assíncrono e gerar um processo anexando
&
:Se você não quiser isso como uma função, você pode fazer 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 diretos dos idiomas. Eles geralmente estão programando de maneira criativa usando recursos de linguagem existentes. Qualquer linguagem que possa armazenar um ponteiro/referência/cópia de algum bloco de código/função/script pode implementar retornos de chamada.
Um retorno de chamada é uma função chamada quando ocorre algum evento. Com
bash
o , o único mecanismo de manipulação de eventos em vigor está relacionado a sinais, a saída do shell e estendido para eventos de erros do shell, eventos de depuração e eventos de retorno de scripts de função/origem.Aqui está um exemplo de um retorno de chamada inútil, mas simples, aproveitando as armadilhas de sinal.
Primeiro crie o script implementando o retorno de chamada:
Em seguida, execute o script em um terminal:
$ ./callback-example
e em outro, enviar o
USR1
sinal para o processo shell.Cada sinal enviado deve acionar a exibição de linhas como estas no primeiro terminal:
ksh93
, como shell implementando muitos recursos quebash
foram 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 shell é modificada ou referenciada (ou seja, lida). Isso abre o caminho para aplicativos orientados a eventos mais interessantes.Por exemplo, esse recurso permitia que retornos de chamada no estilo X11/Xt/Motif em widgets gráficos fossem implementados em uma versão antiga
ksh
que incluía extensões gráficas chamadasdtksh
. Consulte o manual do dksh .