Algunas veces, cuando leí sobre programación, me encontré con el concepto de "devolución de llamada".
Curiosamente, nunca encontré una explicación que pueda llamar "didáctica" o "clara" para este término "función de devolución de llamada" (casi cualquier explicación que leí me pareció lo suficientemente diferente de otra y me sentí confundido).
¿El concepto de "devolución de llamada" de programación existe en Bash? Si es así, responda con un pequeño y simple ejemplo de Bash.
declarative.bash
interesante, como un marco que aprovecha explícitamente las funciones configuradas para ser invocadas cuando se necesita un valor dado.Respuestas:
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:
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):
Si desea probar esto usted mismo, guarde lo anterior en un archivo, por ejemplo
cleanUpOnExit.sh
, hágalo ejecutable y ejecútelo:Mi código aquí nunca llama explícitamente a la
cleanup
función; le dice a Bash cuándo llamarlotrap cleanup EXIT
, es decir , "querido Bash, ejecuta elcleanup
comando cuando salgas" (ycleanup
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 lacleanup
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
(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 labackup
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. Ambosdoonall
ybackup
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 decimosdoonall
que "devuelva la llamada" a cada uno de sus otros argumentos: proporcionamosdoonall
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).
fuente
Primero, es importante tener en cuenta que lo que hace que una función sea una función de devolución de llamada es cómo se usa, no lo que hace. Una devolución de llamada es cuando el código que escribe se llama desde el código que no escribió. Le está pidiendo al sistema que le devuelva la llamada cuando ocurra algún evento en particular.
Un ejemplo de devolución de llamada en la programación de shell son las trampas. Una trampa es una devolución de llamada que no se expresa como una función, sino como un fragmento de código para evaluar. Le está pidiendo al shell que llame a su código cuando el shell recibe una señal particular.
Otro ejemplo de devolución de llamada es la
-exec
acción delfind
comando. El trabajo delfind
comando es recorrer directorios de manera recursiva y procesar cada archivo a su vez. Por defecto, el procesamiento es imprimir el nombre del archivo (implícito-print
), pero con-exec
el procesamiento es ejecutar un comando que usted especifique. Esto se ajusta a la definición de una devolución de llamada, aunque en las devoluciones de llamada, no es muy flexible ya que la devolución de llamada se ejecuta en un proceso separado.Si implementó una función de búsqueda, puede hacer que use una función de devolución de llamada para llamar a cada archivo. Aquí hay una función de búsqueda ultra simplificada que toma un nombre de función (o nombre de comando externo) como argumento y lo llama a todos los archivos normales en el directorio actual y sus subdirectorios. La función se utiliza como una devolución de llamada que se llama cada vez que
call_on_regular_files
encuentra un archivo normal.Las devoluciones de llamada no son tan comunes en la programación de shell como en algunos otros entornos porque los shells están diseñados principalmente para programas simples. Las devoluciones de llamada son más comunes en entornos donde los datos y el flujo de control tienen más probabilidades de moverse hacia adelante y hacia atrás entre las partes del código que se escriben y distribuyen de forma independiente: el sistema base, varias bibliotecas, el código de la aplicación.
fuente
foreach_server() { declare callback="$1"; declare server; for server in 192.168.0.1 192.168.0.2 192.168.0.3; do "$callback" "$server"; done; }
la que usted podría funcionar comoforeach_server echo
,foreach_server nslookup
, etc. Eldeclare callback="$1"
es tan simple como se puede conseguir sin embargo: la devolución de llamada tiene que ser aprobada en algún lugar, o no es una devolución de llamada.Las "devoluciones de llamada" son solo funciones pasadas como argumentos a otras funciones.
A nivel de shell, eso simplemente significa scripts / funciones / comandos pasados como argumentos a otros scripts / funciones / comandos.
Ahora, para un ejemplo simple, considere el siguiente script:
teniendo la sinopsis
se aplicará
filter
a cadafile
argumento, luego llamecommand
con las salidas de los filtros como argumentos.Por ejemplo:
Esto está muy cerca de lo que puedes hacer en lisp (es broma ;-))
Algunas personas insisten en limitar el término "devolución de llamada" a "controlador de eventos" y / o "cierre" (función + datos / tupla de entorno); Este no es el significado generalmente aceptado . Y una de las razones por las que las "devoluciones de llamada" en esos sentidos estrechos no son de mucha utilidad en shell es porque las capacidades de programación de tuberías + paralelismo + dinámico son mucho más potentes, y ya está pagando por ellas en términos de rendimiento, incluso si usted intente usar el shell como una versión torpe de
perl
opython
.fuente
%
interpolación en los filtros, todo el asunto podría reducirse a:cmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")
. Pero eso es mucho menos útil e ilustrativo en mi humilde opinión.$1 <($2 "$3") <($2 "$4")
Mas o menos.
Una forma sencilla de implementar una devolución de llamada en bash es aceptar el nombre de un programa como parámetro, que actúa como "función de devolución de llamada".
Esto se usaría así:
Por supuesto que no tienes cierres en bash. Por lo tanto, la función de devolución de llamada no tiene acceso a las variables en el lado del llamante. Sin embargo, puede almacenar datos que la devolución de llamada necesita en variables de entorno. Pasar la información desde la devolución de llamada al script de invocador es más complicado. Los datos se pueden colocar en un archivo.
Si su diseño permite que todo se maneje en un solo proceso, puede usar una función de shell para la devolución de llamada, y en este caso la función de devolución de llamada tiene, por supuesto, acceso a las variables en el lado del invocador.
fuente
Solo para agregar algunas palabras a las otras respuestas. La función de devolución de llamada funciona en funciones externas a la función que devuelve la llamada. Para que esto sea posible, se debe pasar una definición completa de la función a la que se debe volver a llamar a la función que devuelve la llamada, o su código debe estar disponible para la función que vuelve a llamar.
El primero (pasar código a otra función) es posible, aunque omitiré un ejemplo para esto implicaría complejidad. El último (pasar la función por nombre) es una práctica común, ya que las variables y funciones declaradas fuera del alcance de una función están disponibles en esa función siempre que su definición preceda a la llamada a la función que opera en ellas (que, a su vez , como se declarará antes de que se llame).
También tenga en cuenta que sucede algo similar cuando se exportan funciones. Un shell que importa una función puede tener un marco listo y estar esperando las definiciones de función para ponerlas en acción. La exportación de funciones está presente en Bash y causó problemas anteriormente serios, por cierto (que se llamaba Shellshock):
Completaré esta respuesta con un método más de pasar una función a otra función, que no está explícitamente presente en Bash. Este lo pasa por dirección, no por nombre. Esto se puede encontrar en Perl, por ejemplo. Bash ofrece de esta manera ni funciones ni variables. Pero si, como usted dice, desea tener una imagen más amplia con Bash como ejemplo, entonces debe saber que el código de función puede residir en algún lugar de la memoria, y esa ubicación de memoria puede acceder a ese código, que es llamó a su dirección.
fuente
Uno de los ejemplos más simples de devolución de llamada en bash es uno con el que muchas personas están familiarizadas, pero no se dan cuenta del patrón de diseño que realmente están utilizando:
cron
Cron le permite especificar un ejecutable (un binario o script) al que el programa cron devolverá la llamada cuando se cumplan algunas condiciones (la especificación de tiempo)
Digamos que tiene un guión llamado
doEveryDay.sh
. La forma sin devolución de llamada para escribir el script es:La forma de devolución de llamada para escribirlo es simplemente:
Luego, en crontab, establecerías algo como
Entonces no necesitará escribir el código para esperar a que se active el evento, sino confiar en
cron
devolverle el código.Ahora, considere CÓMO escribiría este código en bash.
¿Cómo ejecutarías otro script / función en bash?
Escribamos una función:
Ahora ha creado una función que acepta una devolución de llamada. Simplemente puede llamarlo así:
Por supuesto, la función every24hours nunca regresa. Bash es un poco único en el sentido de que podemos hacerlo fácilmente asíncrono y generar un proceso agregando
&
:Si no desea esto como una función, puede hacerlo como un script:
Como puede ver, las devoluciones de llamada en bash son triviales. Es sencillo:
Y llamar a la devolución de llamada es simplemente:
Como puede ver en el formulario anterior, las devoluciones de llamada rara vez son características directas de los idiomas. Por lo general, están programando de manera creativa utilizando las funciones de lenguaje existentes. Cualquier lenguaje que pueda almacenar un puntero / referencia / copia de algún bloque de código / función / script puede implementar devoluciones de llamada.
fuente
watch
yfind
(cuando se usan con-exec
parámetros)Una devolución de llamada es una función llamada cuando se produce algún evento. Con
bash
, el único mecanismo de manejo de eventos implementado está relacionado con las señales, la salida del shell y los eventos de errores extendidos al shell, eventos de depuración y eventos de retorno de scripts de función / fuente.Aquí hay un ejemplo de una devolución de llamada inútil pero simple aprovechando trampas de señal.
Primero cree el script implementando la devolución de llamada:
Luego ejecute el script en una terminal:
$ ./callback-example
y en otro, envíe la
USR1
señal al proceso de shell.Cada señal enviada debe activar la visualización de líneas como estas en el primer terminal:
ksh93
, ya que Shell implementa muchas características quebash
luego adoptaron, proporciona lo que llama "funciones de disciplina". Estas funciones, no disponibles conbash
, se invocan cuando una variable de shell se modifica o hace referencia (es decir, se lee). Esto abre el camino a aplicaciones más interesantes impulsadas por eventos.Por ejemplo, esta característica permitió implementar devoluciones de llamada de estilo X11 / Xt / Motif en widgets gráficos en una versión anterior de las
ksh
extensiones gráficas incluidas llamadasdtksh
. Ver el manual de dksh .fuente