Tu Terminal ahora es mucho mas inteligente (gracias a Fish) Vistas: 3

Hace unos días, mientras preparaba el episodio sobre buildah, el constructor de imágenes de Podman, descrubrí que el shell fish tiene un sistema de eventos muy potente que me permite ejecutar funciones automáticamente en respuesta a ciertos eventos del sistema o del shell. No es necesario que tengas un proceso escuchando eventos del sistema, sino que esto te lo ofrece fish completamente de caja, sin que te tengas que calentar la cabeza para hacerlo. Realmente esto es muy potente, para casos como el que comenté en ese episodio. Un ejemplo es cuando tu script puede terminar de forma abrupta, y tienes que limpiar el desastre producido, en estos casos tener al alcance de tus manos estos eventos es sencillamente una genialidad.

0:00 / 0:00

Tu Terminal ahora es mucho mas inteligente (gracias a Fish)

Un pensamiento

Imagina que tienes un script que realiza una serie de operaciones, pero, por la razón que sea falla. En estas operaciones, es posible que deje archivos temporales o que haya detenido un servicio. Al terminar abruptamente tu script, no se eliminan esos archivos temporales o no se reinicia el servicio.

Existen diferentes formas de resolver este final inesperado de tu script, pero una de las formas más elegantes es usar los eventos del shell fish. De esta forma, puedes configurar un evento que se ejecute automáticamente cuando tu script termine, ya sea de forma normal o abrupta, para realizar las tareas de limpieza necesarias.

Por supuesto que existen otras opciones, pero como decía, esta es de las mas elegantes, porque no tienes que preocuparte por el flujo de tu script, ni por las diferentes formas en las que puede terminar, simplemente configuras un evento que se encargue de la limpieza, y listo. Esto es especialmente útil en scripts complejos, donde el flujo de ejecución puede ser difícil de predecir.

Los eventos en fish

Los eventos en el shell fish son una herramienta muy potente para automatizar tareas sin necesidad de complicar en exceso tus funciones principales con lógica de control.

Los eventos mas comunes y útiles en fish son los siguientes,

Nombre del EventoMomento de ejecuciónUso típico
fish_promptAntes de mostrar el promptPersonalización dinámica del prompt.
fish_preexecAntes de ejecutar un comandoLogging o validación de comandos.
fish_postexecDespués de ejecutar un comandoAlertas de fin de tarea o medir tiempos.
fish_exitAl cerrar la sesiónLimpieza de procesos y archivos.
fish_cancelAl pulsar Ctrl+CResetear estados internos.
fish_focus_inAl ganar el foco la ventanaActualizar estado de git o archivos.
fish_focus_outAl perder el foco la ventanaPausar procesos o guardar estados.

Veamos cada uno de estos eventos con un poco más de detalle, y agrupados por su momento de ejecución.

Eventos del ciclo de vida del Shell

Estos controlan lo que ocurre cuando abres o cierras tu sesión de terminal.

  • fish_prompt: Se ejecuta cada vez que el shell está listo para recibir un nuevo comando y necesita pintar el cursor. Es ideal para actualizar contadores o estados visuales.
  • fish_exit: Se dispara justo antes de que el proceso del shell termine (al escribir exit o cerrar la ventana). Es el que usas para tareas de limpieza (cleanup), como borrar archivos temporales o desmontar volúmenes.
  • fish_cancel: Se ejecuta si el usuario cancela la línea de comandos actual presionando Ctrl+C.

Eventos de ejecución de comandos

Permiten interactuar con lo que el usuario escribe antes y después de que ocurra la acción.

  • fish_preexec: Se dispara inmediatamente después de pulsar Enter, pero antes de que el comando se ejecute realmente. Recibe como argumento la línea de comandos completa.
  • fish_postexec: Se ejecuta justo después de que un comando ha terminado. Recibe la línea de comandos y el código de salida ($status). Es perfecto para notificaciones de «comando finalizado».

Donde configurar las funciones

Antes de entrar en los ejemplos, una nota importante. Estas funciones las tienes que poner en el directorio ~/.config/fish/conf.d/. Esto es así, porque para que una función que escucha un evento --on-event se active, debe estar cargada en la memoria de la sesión actual.

Para que fish cargue de forma automática las funciones con eventos, o bien la pegas directamente en la shell, que como ya te puedes imaginar, no es nada práctico, o bien, la pones en el directorio indicado anteriormente.

Si la pones en el directorio ~/.config/fish/funtions, fish no la lee hasta que intentas llamar a la función por su nombre, y como el evento es invisible, la función nunca se carga y el evento nunca se escucha.

Algunos ejemplos prácticos

Dicho esto, es el momento de ver algunos ejemplos prácticos de uso de estos eventos, para que veas la potencia que tienen y cómo pueden ayudarte a automatizar tareas en tu terminal.

El evento fish_prompt

Este evento se dispara justo antes de que el prompt se dibuje en tu pantalla. Es decir, cada vez que terminas de ejecutar un comando y la shell te devuelve el control para que escribas el siguiente, Fish lanza este evento.

Aunque Fish busca por defecto una función llamada fish_prompt para pintar la línea de comandos, tú puedes suscribir otras funciones a este evento. Esto es útil para realizar tareas de mantenimiento silenciosas o actualizar información de estado sin ensuciar la función principal del prompt.

Podemos crear una función que nos avise si existe un archivo Cargo.toml, por ejemplo,

function aviso_proyecto_rust --on-event fish_prompt
    # Comprobamos de forma silenciosa si existe Cargo.toml
    if test -f Cargo.toml
        echo (set_color yellow)"⚠️ No olvides usar 'cargo check' ⚠️"(set_color normal)
    end
end

Otro ejemplo, para mantener la limpieza en tu equipo y no mantener decenas de archivos temporales, podrías crear una función que te avise que tienes archivos temporales o incluso que borre los archivos temporales de mas de un año,

function recordatorio_limpieza --on-event fish_prompt
    set -l temporales (fd -e tmp --max-depth 1 | count)

    if test $temporales -gt 0
        echo (set_color red)"       Tienes $temporales archivos temporales aquí. ¡Mantén tu sistema limpio!"(set_color normal)
    end
end

El evento fish_preexec

Este evento se dispara inmediatamente después de que pulsas Enter, pero justo antes de que el comando empiece a correr. Es el portero de tu terminal.

A diferencia de fish_prompt, este evento recibe un argumento: la línea de comandos completa que acabas de escribir. Esto te permite inspeccionar qué vas a ejecutar y tomar decisiones en milisegundos. Es el lugar perfecto para auditorías, validaciones o para preparar el entorno como iniciar un cronómetro.

Un ejemplo claro es utilizar un registro de todos los comandos peligrosos o importantes que ejecutas, para tener un historial de tus acciones. Por ejemplo, podrías registrar cada vez que ejecutas un comando con sudo,

function auditor_de_comandos --on-event fish_preexec
    # $argv[1] contiene el comando completo que vas a ejecutar
    set -l comando $argv[1]

    # Si el comando contiene 'sudo' o 'rm', lo registramos en un log propio
    if string match -q "*sudo *" $comando; or string match -q "*rm *" $comando
        set -l fecha (date "+%Y-%m-%d %H:%M:%S")
        echo "[$fecha] Ejecutando comando crítico: $comando" >> ~/.config/fish/auditoria_critica.log
        echo (set_color yellow)"󱚟 Registrando acción crítica por seguridad..."(set_color normal)
    end
end

El evento fish_postexec

Este evento se dispara justo después de que un comando termina su ejecución, pero antes de que se vuelva a pintar el prompt. Al igual que su hermano preexec, recibe como argumento la línea de comandos que se acaba de ejecutar. Pero lo mejor es que en este punto tienes acceso a dos variables críticas:

  • $status: El código de salida del comando (0 si fue bien, distinto de 0 si falló).
  • $CMD_DURATION: El tiempo exacto que tardó el comando en ejecutarse (en milisegundos).

Un ejemplo claro de uso es para mostrarte de forma evidente como terminó la ejecución de un comando.

function avisar_error --on-event fish_postexec
    # Guardamos el estado del comando anterior
    set -l ultimo_estado $status
    set -l comando $argv[1]

    if test $ultimo_estado -ne 0
        echo (set_color red -b white)"  "(set_color normal)(set_color red)" El comando '$comando' falló con código $ultimo_estado "(set_color normal)
    end
end

O también para mostrarte el tiempo que ha tardado en compilarse una aplicación en Rust,

function benchmark_rust --on-event fish_postexec
    set -l comando $argv[1]

    if string match -q "cargo build*" $comando
        set -l segundos (math -s2 $CMD_DURATION / 1000)
        echo (set_color green)"󱘗 Compilación finalizada en $segundos segundos. ¡A tope con ese binario!"(set_color normal)
    end
end

Otro ejemplo claro, ahora mirando el mundo Podman, es la posibilidad de mostrarte el estado actual de tus contenedores,

function estado_contenedores_post --on-event fish_postexec
    if string match -q "podman *" $argv[1]
        echo (set_color cyan)"󰡄 Estado actual de tus contenedores:"(set_color normal)
        podman ps --format "table {{.Names}}\t{{.Status}}"
    end
end

El evento fish_exit

Este evento se dispara justo cuando la sesión de la shell va a terminar. Ya sea porque has escrito exit, porque has pulsado Ctrl+D o porque has cerrado la ventana de la terminal.

Es el momento de la limpieza y la persistencia. Es el lugar ideal para tareas de mantenimiento final, como sincronizar archivos de configuración, cerrar conexiones o simplemente despedirte de tu sistema con estilo. A diferencia de otros, este evento no recibe argumentos (el comando ya terminó), pero es tu última oportunidad de ejecutar código antes de que el proceso desaparezca.

A parte de esto, también puedes realizar operaciones de los mas diversas, pero inluso podrías controlar el tiempo que has tenido una sesión abierta, para mostrarte un mensaje de despedida personalizado,

function despedida_atareao --on-event fish_exit
    set -l hora_fin (date +%s)
    set -l duracion_segundos (math $hora_fin - $HORA_INICIO)
    set -l minutos (math $duracion_segundos / 60)

    echo (set_color magenta)"󱫋 ¡Sesión finalizada!"(set_color normal)
    echo "Has estado produciendo durante $minutos minutos. ¡Buen trabajo!"
    sleep 1 # Para que te dé tiempo a leerlo antes de que cierre
end

Para que esto funcione es necesario que en tu config.fish guardes la hora de inicio de tu sesión, para luego poder calcular la duración,

set -g HOTA_INICIO (date +%s)

El evento fish_cancel

Este evento se dispara cuando tienes algo escrito en la línea de comandos pero, en lugar de pulsar Enter para ejecutarlo, pulsas Ctrl+C para abortar.

A diferencia de fish_postexec que se lanza cuando un comando termina, fish_cancel se activa cuando el comando ni siquiera ha llegado a ejecutarse. Es el evento del arrepentimiento. Es muy útil para limpiar la pantalla, resetear estados o simplemente dar un feedback visual de que la acción ha sido cancelada con éxito.

Un ejemplo claro es para limpiar la línea de comandos y mostrar un mensaje de cancelación,

function aviso_comando_cancelado --on-event fish_cancel
    echo (set_color yellow)" 󰚌 Operación abortada. Limpiando el buffer..."(set_color normal)
    sleep 0.3
    commandline -f repaint
end

Los eventos fish_focus_in y fish_focus_out

Vamos con los dos últimos eventos, que se disparan cuando la ventana de tu terminal gana o pierde el foco. Son perfectos para ajustar el comportamiento de tu terminal según si estás trabajando activamente en ella o no.

  • fish_focus_in, se dispara cuando la ventana de tu terminal gana el foco (haces clic en ella o llegas con Alt-Tab).
  • fish_focus_out, se dispara cuando la ventana pierde el foco (te vas a otra aplicación).

Para que esto funcione, tu emulador de terminal (GNOME Terminal, Alacritty, Kitty, etc.) debe soportar las secuencias de escape de Focus Reporting. La mayoría de los modernos lo hacen. Es una herramienta brutal para la productividad, porque te permite automatizar acciones basadas en tu presencia.

Deja una respuesta

Publicar comentario