En la programación imperativa típica , se escriben secuencias de instrucciones y se ejecutan una tras otra, con un flujo de control explícito. Por ejemplo:
if [ -f file1 ]; then # If file1 exists ...
cp file1 file2 # ... create file2 as a copy of a file1
fi
etc.
Como se puede ver en el ejemplo, en la programación imperativa sigue el flujo de ejecución con bastante facilidad, siempre avanzando desde cualquier línea de código dada para determinar su contexto de ejecución, sabiendo que cualquier instrucción que dé se ejecutará como resultado de su ubicación en el flujo (o las ubicaciones de sus sitios de llamadas, si está escribiendo funciones).
Cómo las devoluciones de llamada cambian el flujo
Cuando usa devoluciones de llamada, en lugar de colocar el uso de un conjunto de instrucciones "geográficamente", describe cuándo debe llamarse. Ejemplos típicos en otros entornos de programación son casos como "descargue este recurso, y cuando la descarga esté completa, llame a esta devolución de llamada". Bash no tiene una construcción de devolución de llamada genérica de este tipo, pero tiene devoluciones de llamada, para el manejo de errores y algunas otras situaciones; por ejemplo (primero hay que entender la sustitución de comandos y los modos de salida de Bash para comprender ese ejemplo):
#!/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
Si desea probar esto usted mismo, guarde lo anterior en un archivo, por ejemplo cleanUpOnExit.sh
, hágalo ejecutable y ejecútelo:
chmod 755 cleanUpOnExit.sh
./cleanUpOnExit.sh
Mi código aquí nunca llama explícitamente a la cleanup
función; le dice a Bash cuándo llamarlo trap cleanup EXIT
, es decir , "querido Bash, ejecuta el cleanup
comando cuando salgas" (y cleanup
resulta ser una función que definí anteriormente, pero podría ser cualquier cosa que Bash entienda). Bash admite esto para todas las señales no fatales, salidas, fallas de comandos y depuración general (puede especificar una devolución de llamada que se ejecuta antes de cada comando). La devolución de llamada aquí es la cleanup
función, que Bash "vuelve a llamar" justo antes de que salga el shell.
Puede usar la capacidad de Bash para evaluar los parámetros de shell como comandos, para construir un marco orientado a la devolución de llamadas; eso está algo más allá del alcance de esta respuesta, y tal vez causaría más confusión al sugerir que pasar funciones siempre implica devoluciones de llamada. Ver Bash: pasar una función como parámetro para algunos ejemplos de la funcionalidad subyacente. La idea aquí, al igual que con las devoluciones de llamadas de manejo de eventos, es que las funciones pueden tomar datos como parámetros, pero también otras funciones, esto permite a las personas que llaman proporcionar comportamiento y datos. Un ejemplo simple de este enfoque podría ser
#!/bin/bash
doonall() {
command="$1"
shift
for arg; do
"${command}" "${arg}"
done
}
backup() {
mkdir -p ~/backup
cp "$1" ~/backup
}
doonall backup "$@"
(Sé que esto es un poco inútil ya que cp
puede manejar múltiples archivos, es solo para ilustración).
Aquí creamos una función, doonall
que toma otro comando, dado como parámetro, y lo aplica al resto de sus parámetros; luego usamos eso para llamar a la backup
función en todos los parámetros dados al script. El resultado es un script que copia todos sus argumentos, uno por uno, en un directorio de respaldo.
Este tipo de enfoque permite que las funciones se escriban con responsabilidades únicas: doonall
la responsabilidad es ejecutar algo en todos sus argumentos, uno a la vez; backup
La responsabilidad de él es hacer una copia de su (único) argumento en un directorio de respaldo. Ambos doonall
y backup
se pueden usar en otros contextos, lo que permite una mayor reutilización del código, mejores pruebas, etc.
En este caso, la devolución de llamada es la backup
función, a la que le decimos doonall
que "devuelva la llamada" a cada uno de sus otros argumentos: proporcionamos doonall
comportamiento (su primer argumento) así como datos (los argumentos restantes).
(Tenga en cuenta que en el tipo de caso de uso demostrado en el segundo ejemplo, yo no usaría el término "devolución de llamada", pero ese es quizás un hábito resultante de los idiomas que uso. Pienso en esto como pasar funciones o lambdas alrededor , en lugar de registrar devoluciones de llamada en un sistema orientado a eventos).