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:
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 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):
#!/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 tentar fazer isso sozinho, salve o acima em um arquivo, por exemplo cleanUpOnExit.sh
, torne-o executável e execute-o:
chmod 755 cleanUpOnExit.sh
./cleanUpOnExit.sh
Meu código aqui nunca chama explicitamente a cleanup
função; ele diz ao Bash quando chamá-lo, usando trap cleanup EXIT
, ou seja , “caro Bash, execute o cleanup
comando quando você sair” (e cleanup
é 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 é a cleanup
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
#!/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 pega outro comando, dado como parâmetro, e a aplica ao restante de seus parâmetros; então usamos isso para chamar a backup
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. Ambos doonall
e 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 backup
função, que dizemos doonall
para "retornar" em cada um de seus outros argumentos - fornecemos tanto o doonall
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.)