Nella tipica programmazione imperativa , si scrivono sequenze di istruzioni e vengono eseguite una dopo l'altra, con flusso di controllo esplicito. Per esempio:
if [ -f file1 ]; then # If file1 exists ...
cp file1 file2 # ... create file2 as a copy of a file1
fi
eccetera.
Come si può vedere dall'esempio, nella programmazione imperativa segui abbastanza facilmente il flusso di esecuzione, risalendo sempre da una determinata riga di codice per determinarne il contesto di esecuzione, sapendo che tutte le istruzioni che darai verranno eseguite come risultato della loro posizione nel flusso (o posizioni dei relativi siti di chiamata, se si stanno scrivendo funzioni).
Come i callback cambiano il flusso
Quando si utilizzano i callback, invece di inserire "geograficamente" un insieme di istruzioni, si descrive quando deve essere chiamato. Esempi tipici in altri ambienti di programmazione sono casi come "scarica questa risorsa e quando il download è completo, chiama questo callback". Bash non ha un costrutto di callback generico di questo tipo, ma ha callback, per la gestione degli errori e alcune altre situazioni; per esempio ( per capire quell'esempio bisogna prima capire la sostituzione dei comandi e le modalità di uscita di Bash ):
#!/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 vuoi provarlo tu stesso, salva quanto sopra in un file, diciamo cleanUpOnExit.sh
, rendilo eseguibile ed eseguilo:
chmod 755 cleanUpOnExit.sh
./cleanUpOnExit.sh
Il mio codice qui non chiama mai esplicitamente la cleanup
funzione; dice a Bash quando chiamarlo, usando trap cleanup EXIT
, ad esempio , "caro Bash, per favore esegui il cleanup
comando quando esci" (e cleanup
sembra essere una funzione che ho definito in precedenza, ma potrebbe essere qualunque cosa capisca Bash). Bash lo supporta per tutti i segnali non fatali, le uscite, gli errori di comando e il debug generale (è possibile specificare un callback che viene eseguito prima di ogni comando). Il callback qui è la cleanup
funzione, che viene "richiamata" da Bash appena prima che la shell esca.
È possibile utilizzare la capacità di Bash di valutare i parametri della shell come comandi, per creare un framework orientato alla callback; questo è un po 'oltre lo scopo di questa risposta, e forse causerebbe più confusione suggerendo che il passaggio di funzioni comporta sempre callback. Vedi Bash: passa una funzione come parametro per alcuni esempi della funzionalità sottostante. L'idea qui, come per i callback di gestione degli eventi, è che le funzioni possono prendere i dati come parametri, ma anche altre funzioni: ciò consente ai chiamanti di fornire comportamenti e dati. Un semplice esempio di questo approccio potrebbe apparire
#!/bin/bash
doonall() {
command="$1"
shift
for arg; do
"${command}" "${arg}"
done
}
backup() {
mkdir -p ~/backup
cp "$1" ~/backup
}
doonall backup "$@"
(So che questo è un po 'inutile poiché cp
può gestire più file, è solo a scopo illustrativo.)
Qui creiamo una funzione, doonall
che accetta un altro comando, dato come parametro, e la applica al resto dei suoi parametri; quindi usiamo quello per chiamare la backup
funzione su tutti i parametri dati allo script. Il risultato è uno script che copia tutti i suoi argomenti, uno per uno, in una directory di backup.
Questo tipo di approccio consente di scrivere funzioni con singole responsabilità: doonall
la responsabilità è di eseguire qualcosa su tutti i suoi argomenti, uno alla volta; backup
La responsabilità è di fare una copia del suo (unico) argomento in una directory di backup. Entrambi doonall
e backup
possono essere utilizzati in altri contesti, il che consente un maggiore riutilizzo del codice, test migliori ecc.
In questo caso il callback è la backup
funzione, che diciamo doonall
di "richiamare" su ciascuno dei suoi altri argomenti: forniamo il doonall
comportamento (il suo primo argomento) così come i dati (gli argomenti rimanenti).
(Si noti che nel tipo di caso d'uso dimostrato nel secondo esempio, non userei il termine "callback", ma questa è forse un'abitudine derivante dalle lingue che uso. Penso a questo come funzioni di passaggio o lambda in giro , piuttosto che registrare callback in un sistema orientato agli eventi.)