Na típica programação imperativa , 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:
if [ -f file1 ]; then # If file1 exists ...
cp file1 file2 # ... create file2 as a copy of a file1
fi
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 dadas serão executadas como um resultado de sua localização no fluxo (ou nas localizações de seus locais de chamada, se você estiver escrevendo funções).
Como as chamadas de retorno mudam o fluxo
Quando você usa callbacks, 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 “baixe este recurso e, quando o download estiver completo, chame este retorno de chamada”. O Bash não tem uma construção de retorno de chamada genérica desse tipo, mas ele possui callbacks, para tratamento de erros e algumas outras situações; por exemplo (primeiro é preciso entender substituição de comandos e Bash exit modes para entender esse exemplo):
#!/bin/bash
scripttmp=$(mktemp -d) # Create a temporary directory (these will usually be created under /tmp or /var/tmp/)
cleanup() { # Declare a cleanup function
rm -rf "${scripttmp}" # ... which deletes the temporary directory we just created
}
trap cleanup EXIT # Ask Bash to call cleanup on exit
Se você quiser testar isso sozinho, salve o arquivo acima, diga cleanUpOnExit.sh
, torne-o executável e execute-o:
chmod 755 cleanUpOnExit.sh
./cleanUpOnExit.sh
Meu código aqui nunca chama explicitamente a função cleanup
; ele diz ao Bash quando chamá-lo, usando trap cleanup EXIT
, ie “querido Bash, por favor execute o comando cleanup
quando você sair” (e cleanup
é uma função que defini anteriormente, mas pode ser qualquer coisa que 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 é a função cleanup
, que é “chamada de volta” pelo Bash logo antes da saída do shell.
Você pode usar a capacidade do Bash para avaliar os parâmetros do shell como comandos, para criar um framework orientado ao retorno de chamada; Isso está um pouco além do escopo dessa resposta e talvez cause mais confusão sugerindo que a passagem de funções sempre envolva retornos de chamada. Veja Bash: passe uma função como parâmetro para alguns exemplos da funcionalidade subjacente. A ideia aqui, como ocorre com retornos de chamada de tratamento de eventos, é que as funções podem usar dados como parâmetros, mas também outras funções - isso permite que os chamadores forneçam tanto comportamento quanto dados. Um exemplo simples dessa abordagem poderia se parecer com
#!/bin/bash
doonall() {
command="$1"
shift
for arg; do
"${command}" "${arg}"
done
}
backup() {
mkdir -p ~/backup
cp "$1" ~/backup
}
doonall backup "$@"
(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 aplica-o ao resto de seus parâmetros; então usamos isso para chamar a função backup
em todos os parâmetros fornecidos para o 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
é responsável por executar algo em todos os seus argumentos, um de cada vez; A responsabilidade de backup
é fazer uma cópia de seu argumento (único) em um diretório de backup. Tanto doonall
como backup
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 função backup
, que diz doonall
para "chamar de volta" em cada um dos seus outros argumentos - nós fornecemos doonall
com 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 "callback", mas talvez seja um hábito resultante dos idiomas que uso. Penso nisso como funções passageiras ou lambdas, em vez de registrar retornos de chamada em um sistema orientado a eventos.)