En nuestro trabajo diario peleando con sesiones de terminal hay ocasiones en las que, teniendo una sesión de terminal abierta, no sabemos a qué hora se ejecutó un comando determinado. O acabamos de iniciar una tarea que tiene pinta de ser muy larga y nos gustaría que el ordenador nos avisara cuando termine para no estar mirando cada poco tiempo. Además, seguro que a ti también te ha pasado, te acuerdas de que necesitas el aviso cuando la tarea está iniciada y no puedes pararla.
Pequeña introducción
No pretendo crear un sistema muy complejo para este propósito, para eso tenemos auditd, del que hablaré en próximos posts. Este es un pequeño sistema que consume pocos recursos y se dedica a:
- Escribir en el log de sistema los comandos que se van ejecutando cuando concluyen.
- Informar en la ventana de nuestra terminal de la hora que es, de lo que ha tardado en ejecutar un cierto comando y la carga del sistema en ese momento. Podremos configurarlo y mostrar más cosas.
- Notificar con un programa externo cuando una orden ha finalizado. Ya sea por medio de notificación de escritorio, ventana emergente, destacando la ventana de terminal, o incluso enviando un webhook, ya depende de nosotros.
Podemos ver, tras la finalización de un comando que ha tardado más de 2 segundos (por ejemplo, comando_largo) el siguiente mensaje, notificación:
Además, como ha tardado más de 10 segundos (los tiempo podremos configurarlos), veremos lo siguiente en el escritorio:
Por supuesto, podemos elegir desactivar/activar las notificaciones, o cómo se va a notificar desde otra ventana mientras la tarea está en ejecución.
Pongo aquí una primera versión del script. Ya que se me ocurren muchas mejoras, y pequeños cambios que podemos hacer para enriquecer la experiencia aún más.
En principio el archivo lo llamé bashistant.sh (que viene de Bash Assistant, todo mezclado). Y quiero modificarlo un poco para integrarlo en los gscripts.
Activación
Para poder utilizar este script automáticamente en nuestras sesiones de terminal, podemos editar nuestro archivo ~/.bashrc y añadir la siguiente línea (cambiando la ruta del archivo por la adecuada en tu sistema):
También podemos utilizar los archivo $HOME/.profile o /etc/profile. El último para instalar a nivel de sistema para todos los usuarios.
En principio se creará el directorio .bashistant en tu $HOME para almacenar información sobre las notificaciones, aunque en el futuro se utilizará para más cosas. La inicialización no es muy pesada. Aparte de crear el directorio mencionado anteriormente, obtenemos el ID del proceso emulador de terminal (konsole, xfce4-terminal, gnome-terminal...), y si estamos en un entorno gráfico, obtiene el ID de la ventana que lo gobierna, para resaltar la ventana cuando no nos encontramos visualizándola.
Log de comandos
Esta es la parte menos currada por el momento, se limita a llamar a logger con el comando una vez finalizado. Podemos ver un fragmento de /var/log/syslog aquí:
Dec 18 14:18:04 gaspy-ReDDraG0N Bashistant[25301]: (gaspy) Time: 0sec Command: cat pkey.pem
Dec 18 14:18:50 gaspy-ReDDraG0N Bashistant[25301]: message repeated 2 times: [ (malvado) Time: 4sec Command: rm -rf archivos_confidenciales]
Dec 18 14:43:48 gaspy-ReDDraG0N Bashistant[25301]: (gaspy) Time: 578sec Command: find -name 'confidencial'
Dec 18 16:24:34 gaspy-ReDDraG0N Bashistant[10252]: (gaspy) Time: 0sec Command: pgrep -f apache
Y esto podría delatar al usuario malvado, que normalmente no debe tener permiso para editar logs ni nada parecido.
Este log podemos desactivarlo haciendo:
Si queremos pillar a alguien que ha ejecutado comandos malignos (que no es el cometido de este script), podríamos desactivar esta característica en el código.
Información tras la ejecución
En realidad esto lo encontré en el GitHub de Chuan Ji mientras buscaba información y me gustó la visualización que hacía tras cada comando. No le copié el código, como vemos, su proyecto tiene más precisión midiendo el tiempo. A mí, para Bash, no me interesaba tener demasiada precisión en ello. Pero además, quise completarlo y hacerlo algo más personalizable.
Para modificar el comportamiento de esta característica tenemos una serie de variables que podremos modificar desde nuestro terminal:
- BASHISTANT_INFO_ENABLE=[0,1] : Activa o desactiva esta característica
- BASHISTANT_SHOW_TIME=[n] : Número de segundos que debe tardar la tarea para mostrar esta línea. Podemos hacer que si un comando tarda demasiado poco no se muestre nada, o 0 si queremos que se muestre siempre.
- BASHISTANT_NOW_FORMAT="%d/%m/%Y %H:%M:%S": Formato de fecha y hora (se usa el comando date.
- BASHISTANT_NOW_COLOR=[color]: Código ANSI del color para mostrar la fecha y hora actuales.
- BASHISTANT_INFO_ALIGN=[left|right] : Alineación del texto de información (izquierda o derecha).
- BASHISTANT_INFO_PADDING=" ": Texto separador para que el recuadro no esté pegado al borde de la pantalla.
- BASHISTANT_ELAPSED_COLOR=[color]: Código de color para el tiempo transcurrido en la ejecución.
- BASHISTANT_LOAD_COLOR=[color]: Código de color para la carga del sistema.
- BASHISTANT_INFO_FORMAT="[ %NOW | %ELAPSED | %CPULOAD ]": Formato por el que se muestra la información.
Después de unos días de uso se ha convertido en una buena herramienta sobre todo para determinar de un vistazo cuánto tiempo llevas trabajando en un servidor o necesitas saber cuánto ha tardado una tarea que has olvidado cronometrar. Sí, en ocasiones, lanzamos tareas como mysqldump, restauraciones de base de datos, instalaciones, orquestaciones de un sistema, etc. En definitiva, tareas que pueden llegar a tardar incluso varias horas y que, muchas veces te interesa saber cuánto tiempo han necesitado esas tareas, por ejemplo para hacer documentación. Pero cuando te acuerdas de que deberías cronometrarlo (bastaría con ejecutar un comando poniendo time delante), es cuando el proceso ha terminado, puede llevar ya una hora y no vas a cancelar el proceso a estas alturas... espero no ser el único al que le pasa esto 🙂
Configurar notificaciones
El archivo $HOME/.bashistant/notify lo podremos modificar con un editor de textos para introducir el comando que queremos que se ejecute para notificar la finalización de la orden de Bash. Igual que en el punto anterior, muchas veces puedo lanzar un comando que necesitará mucho tiempo para finalizar y me pongo a hacer otra cosa. Es en esos casos en los que olvido esa primera tarea que dejé haciéndose y puede pasar mucho tiempo hasta que me acuerdo de ella. Una solución rápida, sería ejecutar el comando así:
Pero, como siempre, se me olvida hacer esto cuando voy a ejecutar la orden. Otra opción sería:
Es decir, una vez hemos lanzazdo el comando, pulsamos Control+Z para pausarlo y luego utilizamos fg para reanudarlo, haciendo que una vez reanudado se ejecute zenity para notificar su finalización. Aunque muchas veces no se pueden pausar los comandos sin cargarnos algo.
Así que este script, cuando termina la ejecución de la orden, mirará el archivo ~/.bashistant/notify para ver qué comando tiene que ejecutar. Lo que significa que, aunque la tarea esté en ejecución puedo modificar el contenido de ese archivo, que cuando el comando termine se leerá y se ejecutará lo que ahí diga. Para agilizar un poco más la definición de notificaciones podemos utilizar en el mismo comando de notificación las siguientes palabras clave:
- %ELAPSED%: Para mostrar el tiempo empleado en segundos.
- %COMMAND%: Para mostrar el comando que acaba de finalizar.
- %USER%: Para mostrar el usuario que ha ejecutado el comando
- %HOSTNAME%: Para mostrar el hostname del equipo
Por lo que el archivo ~/.bashistant/notify quedaría así:
Además, disponemos de algunos comandos rápidos como:
- @notify : Para ejecutar notify-send
- @zenity : Para generar un diálogo ded zenity
- @bringtofront : Para traer al primer plano el terminal
Que permiten que ~/.bashistant/notify contenga:
Para realizar la función.
También podemos utilizar el comando notify para definir el comando de notificación. Por lo que, podemos ejecutar un comando muy largo en un terminal, y luego desde otro sitio (con el mismo usuario), hacer:
Así, cuando termine el primer comando (el que nos lleva mucho tiempo), se traerá a primer plano la ventana de terminal desde la que se disparó.
Por otro lado, como sería muy pesado estar todo el rato viendo notificaciones de comandos terminados, ya que un simple cd o ls dispararía la notificación tenemos las variables:
- BASHISTANT_NOTIFY_ENABLE=[0|1]: Que podemos usar para activar o desactivar la característica.
- BASHISTANT_NOTIFY_TIME=[n]: Con la que podemos decir el tiempo mínimo para que una tarea se notifique. Por defecto vale 10, quiere decir que si una tarea lleva menos de 10 segundos, no disparará la notificación.
Más posibilidades
En el comando de notificaciones podríamos, por ejemplo, reproducir un sonido, o una voz, o generar una línea con cURL que dispare un webhook de slack, gitlab o cualquier otra aplicación. Podemos programar el envío de un e-mail. O incluso podemos ejecutar varios comandos, uno por línea.
Si miráis el código podéis ver que hay ciertas cosas que no se usan, por ahora, como la captura de los comandos justo antes de que se ejecuten, o la variable MYPIDS reservada para una futura sorpresa en la siguiente versión. Eso sí, estoy abierto a sugerencias y, por supuesto, os invito a contribuir con este pequeño script.
Foto principal: unsplash-logo Mathew Schwartz