Cómo usar señales de Linux en scripts Bash
Publicado: 2022-08-09
El kernel de Linux envía señales a los procesos sobre los eventos a los que deben reaccionar. Los scripts que se comportan bien manejan las señales con elegancia y solidez y pueden limpiarse por sí solos incluso si presiona Ctrl+C. Así es cómo.
Señales y Procesos
Las señales son mensajes breves, rápidos y unidireccionales que se envían a procesos como scripts, programas y demonios. Le informan al proceso sobre algo que ha sucedido. Es posible que el usuario haya presionado Ctrl+C o que la aplicación haya intentado escribir en la memoria a la que no tiene acceso.
Si el autor del proceso anticipó que se le podría enviar una determinada señal, puede escribir una rutina en el programa o script para manejar esa señal. Esta rutina se denomina manejador de señales . Atrapa o atrapa la señal y realiza alguna acción en respuesta a ella.
Linux usa muchas señales, como veremos, pero desde el punto de vista de las secuencias de comandos, solo hay un pequeño subconjunto de señales que probablemente le interesen. En particular, en las secuencias de comandos no triviales, las señales que indican la la secuencia de comandos para apagar debe quedar atrapada (siempre que sea posible) y se debe realizar un apagado correcto.
Por ejemplo, los scripts que crean archivos temporales o abren puertos de firewall pueden tener la oportunidad de eliminar los archivos temporales o cerrar los puertos antes de que se apaguen. Si el script simplemente muere en el instante en que recibe la señal, su computadora puede quedar en un estado impredecible.
Así es como puede manejar las señales en sus propios scripts.
Conoce las Señales
Algunos comandos de Linux tienen nombres crípticos. No así el mando que atrapa señales. Se llama trap
. También podemos usar trap
con la opción -l
(lista) para mostrarnos la lista completa de señales que usa Linux.
trampa -l
Aunque nuestra lista numerada termina en 64, en realidad hay 62 señales. Faltan las señales 32 y 33. No están implementados en Linux. Han sido reemplazados por funcionalidad en el compilador gcc
para manejar subprocesos en tiempo real. Todo, desde la señal 34, SIGRTMIN
, hasta la señal 64, SIGRTMAX
, son señales en tiempo real.
Verá diferentes listas en diferentes sistemas operativos similares a Unix. En OpenIndiana, por ejemplo, las señales 32 y 33 están presentes, junto con un montón de señales adicionales, lo que eleva el total a 73.
Las señales se pueden referenciar por nombre, número o por su nombre abreviado. Su nombre abreviado es simplemente su nombre sin el "SIG" inicial.
Las señales se emiten por muchas razones diferentes. Si puedes descifrarlos, su propósito está contenido en su nombre. El impacto de una señal cae en una de las siguientes categorías:
- Terminar: El proceso se termina.
- Ignorar: La señal no afecta el proceso. Esta es una señal de solo información.
- Núcleo: se crea un archivo de volcado de núcleo. Esto generalmente se hace porque el proceso ha transgredido de alguna manera, como una violación de la memoria.
- Detener: El proceso se detiene. Es decir, está en pausa , no terminado.
- Continuar: le dice a un proceso detenido que continúe la ejecución.
Estas son las señales que encontrará con más frecuencia.
- SIGHUP : señal 1. La conexión a un host remoto, como un servidor SSH, se interrumpió inesperadamente o el usuario cerró la sesión. Una secuencia de comandos que recibe esta señal puede terminar correctamente o puede optar por intentar volver a conectarse al host remoto.
- SIGINT : Señal 2. El usuario ha presionado la combinación Ctrl+C para forzar el cierre de un proceso, o se ha utilizado el comando
kill
con la señal 2. Técnicamente, esta es una señal de interrupción, no una señal de terminación, sino un script interrumpido sin un controlador de señal generalmente terminará. - SIGQUIT : Señal 3. El usuario ha presionado la combinación Ctrl+D para forzar la salida de un proceso, o se ha utilizado el comando
kill
con la señal 3. - SIGFPE : Señal 8. El proceso intentó realizar una operación matemática ilegal (imposible), como la división por cero.
- SIGKILL : Señal 9. Esta es la señal equivalente a una guillotina. No puedes atraparlo o ignorarlo, y sucede instantáneamente. El proceso se termina inmediatamente.
- SIGTERM : Señal 15. Esta es la versión más considerada de
SIGKILL
.SIGTERM
también le dice a un proceso que finalice, pero puede quedar atrapado y el proceso puede ejecutar sus procesos de limpieza antes de cerrarse. Esto permite un apagado elegante. Esta es la señal predeterminada emitida por el comandokill
.
Señales en la línea de comando
Una forma de atrapar una señal es usar trap
con el número o nombre de la señal y una respuesta que desea que suceda si se recibe la señal. Podemos demostrar esto en una ventana de terminal.
Este comando atrapa la señal SIGINT
. La respuesta es imprimir una línea de texto en la ventana del terminal. Estamos usando la opción -e
(habilitar escapes) con echo
para que podamos usar el especificador de formato " \n
".
trap 'echo -e "\nCtrl+c Detectado."' SIGINT
Nuestra línea de texto se imprime cada vez que pulsamos la combinación Ctrl+C.
Para ver si hay una trampa en una señal, use la opción -p
(imprimir trampa).
trampa -p SIGINT
Usar trap
sin opciones hace lo mismo.
Para restablecer la señal a su estado normal sin atrapar, use un guión " -
" y el nombre de la señal atrapada.
trampa - SIGINT
trampa -p SIGINT
Si no hay salida del comando trap -p
, indica que no hay una trampa configurada en esa señal.
Señales de reventado en secuencias de comandos
Podemos usar el mismo comando trap
de formato general dentro de un script. Este script atrapa tres señales diferentes, SIGINT
, SIGQUIT
y SIGTERM
.
#!/bin/bash trap "echo I was SIGINT terminado; exit" SIGINT trampa "echo estaba terminado con SIGQUIT; salir" SIGQUIT trap "echo I was SIGTERM terminado; exit" SIGTERM eco $$ contador=0 mientras que es cierto hacer echo "Número de bucle:" $((++contador)) dormir 1 hecho
Las tres declaraciones de trap
están en la parte superior del script. Tenga en cuenta que hemos incluido el comando de exit
dentro de la respuesta a cada una de las señales. Esto significa que el script reacciona a la señal y luego sale.
Copie el texto en su editor y guárdelo en un archivo llamado "simple-loop.sh", y hágalo ejecutable usando el comando chmod
. Deberá hacer eso con todos los scripts de este artículo si desea seguirlos en su propia computadora. Simplemente use el nombre del script apropiado en cada caso.

chmod +x bucle-simple.sh
El resto del guión es muy simple. Necesitamos saber el ID de proceso del script, por lo que el script nos lo hace eco. La variable $$
contiene el ID de proceso del script.
Creamos una variable llamada counter
y la ponemos a cero.
El bucle while
se ejecutará para siempre a menos que se detenga a la fuerza. Incrementa la variable del counter
, la repite en la pantalla y duerme por un segundo.
Ejecutemos el script y enviemos diferentes señales.
./bucle-simple.sh
Cuando presionamos "Ctrl + C", nuestro mensaje se imprime en la ventana del terminal y el script finaliza.
Ejecutémoslo de nuevo y enviemos la señal SIGQUIT
usando el comando kill
. Tendremos que hacerlo desde otra ventana de terminal. Tendrá que usar el ID de proceso informado por su propia secuencia de comandos.
./bucle-simple.sh
matar -SIGQUIT 4575
Como se esperaba, el script informa que la señal llega y luego termina. Y finalmente, para probar el punto, lo haremos nuevamente con la señal SIGTERM
.
./bucle-simple.sh
matar -SIGTERM 4584
Hemos verificado que podemos atrapar múltiples señales en un script y reaccionar a cada una de ellas de forma independiente. El paso que promueve todo esto de interesante a útil es agregar controladores de señal.
Manejo de señales en scripts
Podemos reemplazar la cadena de respuesta con el nombre de una función en su script. El comando trap
luego llama a esa función cuando se detecta la señal.
Copie este texto en un editor y guárdelo como un archivo llamado "grace.sh", y hágalo ejecutable con chmod
.
#!/bin/bash trap graceful_shutdown SIGINT SIGQUIT SIGTERM cierre_agraciado() { echo -e "\nEliminando archivo temporal:" $temp_file rm -rf "$archivo_temp" salida } archivo_temp=$(mktemp -p /tmp tmp.XXXXXXXXXX) echo "Archivo temporal creado:" $temp_file contador=0 mientras que es cierto hacer echo "Número de bucle:" $((++contador)) dormir 1 hecho
El script establece una trampa para tres señales diferentes: SIGHUP
, SIGINT
y SIGTERM
, utilizando una sola declaración de trap
. La respuesta es el nombre de la función graceful_shutdown()
. La función se llama cada vez que se recibe una de las tres señales atrapadas.
El script crea un archivo temporal en el directorio “/tmp”, usando mktemp
. La plantilla de nombre de archivo es "tmp.XXXXXXXXXX", por lo que el nombre del archivo será "tmp". seguido de diez caracteres alfanuméricos aleatorios. El nombre del archivo se repite en la pantalla.
El resto del script es igual al anterior, con una variable counter
y un ciclo while
infinito.
./gracia.sh
Cuando se envía una señal al archivo que hace que se cierre, se llama a la función graceful_shutdown()
. Esto elimina nuestro único archivo temporal. En una situación del mundo real, podría realizar cualquier limpieza que requiera su secuencia de comandos.
Además, agrupamos todas nuestras señales atrapadas y las manejamos con una sola función. Puede atrapar señales individualmente y enviarlas a sus propias funciones de controlador dedicadas.
Copie este texto y guárdelo en un archivo llamado “triple.sh”, y hágalo ejecutable usando el comando chmod
.
#!/bin/bash trampa sigint_handler SIGINT trampa sigusr1_handler SIGUSR1 trampa exit_handler SALIR función sigint_handler() { ((++sigint_count)) echo -e "\nSIGINT recibió $sigint_count time(s)". if [[ "$sigint_count" -eq 3 ]]; después echo "Iniciando cierre". bucle_bandera=1 fi } función sigusr1_handler() { echo "SIGUSR1 envió y recibió $((++sigusr1_count)) tiempo(s)." } función exit_handler() { echo "Controlador de salida: el script se está cerrando..." } eco $$ sigusr1_count=0 sigint_count=0 bucle_bandera=0 while [[ $loop_flag -eq 0 ]]; hacer matar -SIGUSR1 $$ dormir 1 hecho
Definimos tres trampas en la parte superior del script.
- Uno atrapa
SIGINT
y tiene un controlador llamadosigint_handler()
. - El segundo atrapa una señal llamada
SIGUSR1
y usa un controlador llamadosigusr1_handler()
. - La trampa número tres atrapa la señal de
EXIT
. Esta señal la emite el propio script cuando se cierra. Establecer un controlador de señal paraEXIT
significa que puede establecer una función que siempre se llamará cuando finalice el script (a menos que se elimine con la señalSIGKILL
). Nuestro controlador se llamaexit_handler()
.
SIGUSR1
y SIGUSR2
son señales proporcionadas para que pueda enviar señales personalizadas a sus scripts. La forma en que los interprete y reaccione ante ellos depende totalmente de usted.
Dejando a un lado los controladores de señales por ahora, el cuerpo del script debería resultarle familiar. Reproduce el ID del proceso en la ventana del terminal y crea algunas variables. La variable sigusr1_count
registra la cantidad de veces que se manejó SIGUSR1
y sigint_count
registra la cantidad de veces que se manejó SIGINT
. La variable loop_flag
se establece en cero.
El bucle while
no es un bucle infinito. Detendrá el bucle si la variable loop_flag
se establece en cualquier valor distinto de cero. Cada giro del bucle while
usa kill
para enviar la señal SIGUSR1
a este script, enviándola al ID de proceso del script. ¡Los scripts pueden enviarse señales a sí mismos!
La función sigusr1_handler()
incrementa la variable sigusr1_count
y envía un mensaje a la ventana del terminal.
Cada vez que se recibe la señal SIGINT
, la función siguint_handler()
incrementa la variable sigint_count
y repite su valor en la ventana del terminal.
Si la variable sigint_count
es igual a tres, la variable loop_flag
se establece en uno y se envía un mensaje a la ventana del terminal para informar al usuario que el proceso de apagado ha comenzado.
Debido a que loop_flag
ya no es igual a cero, el ciclo while
termina y el script finaliza. Pero esa acción genera automáticamente la señal EXIT
y se llama a la función exit_handler()
.
./triple.sh
Después de presionar tres veces Ctrl+C, el script finaliza e invoca automáticamente la función exit_handler()
.
Leer las señales
Al atrapar señales y manejarlas en funciones de controlador sencillas, puede hacer que sus scripts de Bash se ordenen detrás de sí mismos, incluso si se terminan inesperadamente. Eso le da un sistema de archivos más limpio. También evita la inestabilidad la próxima vez que ejecute la secuencia de comandos y, según cuál sea el propósito de la secuencia de comandos, incluso podría evitar brechas de seguridad.
RELACIONADO: Cómo auditar la seguridad de su sistema Linux con Lynis