Traps: haz tus scripts de bash más robustos

5
1508

Navegando por la red nacional me encontré con un interesante artículo (el cual traigo hacia acá textualmente porque está magistralmente explicado) donde su autor nos muestra como hacer que nuestros scripts de Bash sean más robustos usando Traps.

Haz tus scripts de bash más robustos con traps

Imagina que tienes un script de bash que se ejecute todos los días cada seis horas y que en algún momento falle o que ese mismo script se ejecute dos veces simultáneamente. Estas dos situaciones son bastante incómodas ya que requieren de la intervención humana para ser corregidas o en ciertos momentos no pueden ser atendidas dejando el sistema en un estado inconsistente. La solución a esto, entre otras es usar traps.


Traps es una manera sencilla y efectiva de controlar la salida de los scripts de bash. Volvamos a la misma situación inicial, si el script es detenido manualmente, por ejemplo con ctrl-c, se interrumpe devolviendo la señal de salida

INT

y si se termina con

kill

entonces la salida sería

TERM.

Todos los códigos de salida posibles se pueden ver con

kill -l

sin embargo los más utilizados son precisamente

INT, TERM, EXIT

Si el script consiste, por ejemplo, en la sincronización de archivos con

rsync

lo más sensato es apoyarse en un archivo lock que no permita que el script se ejecute simultáneamente:

LOCK="/var/run/rsync.lock"

if [ ! -e $LOCK ]; then
    touch $LOCK
    rsync -avz foo bar
    rm $LOCK
else
   echo "rsync ya se está ejecutando"
fi

En español plano, el script anterior comprueba si existe el archivo lock y si este no existe lo crea y posteriormente ejecuta el comando correspondiente, por último elimina el archivo lock. Si existe el archivo el script simplemente envía un mensaje al usuario indicándole que ya el comando se está ejecutando.

Sin embargo cuando una hay una situación problemática pudiera pasar que el archivo lock no se elimine dando al traste con efectos indeseados. La solución es bien sencilla:

LOCK="/var/run/rsync.lock"

if [ ! -e $LOCK ]; then
   trap "rm -f $LOCK; exit" INT TERM EXIT
   touch $LOCK
   rsync -avz foo bar
   rm $LOCK
   trap - INT TERM EXIT
else
   echo "rsync ya se está ejecutando"
fi

La particularidad de esta solución es que el comando está encerrado en un trap, de modo que cuando se recibe una señal

INT, TERM, EXIT

el script se detiene y borra el archivo lock.

Vale la pena decir que pudiera darse una situación de competencia en el script anterior entre el tiempo en que se verifica el archivo lock y el tiempo en que este se crea. Una posible solución sería usar una redirección y el modo noclobber de bash que no redirige a un archivo existente:

LOCK="/var/run/rsync.lock"

if (set -o noclobber; echo $$ > "$LOCK") 2> /dev/null;
then
    trap 'rm -f "$LOCK"; exit $?' INT TERM EXIT
    rsync -avz foo bar
    rm -f $LOCK
    trap - INT TERM EXIT
    else
        echo "rsync ya se está ejecutando: $(cat $LCK)"
fi

La particularidad de este último es que se usa como ya había dicho, el modo noclobber y que el archivo lock contiene el PID del proceso que se ejecuta.

También vale la pena mencionar que existen otras soluciones como

flock

o

solo

sin embargo en esta entrada quise compartir las soluciones con recursos propios de bash. Pueden aprender un poco más sobre Traps con esta excelente guía.

5 COMENTARIOS

  1. Buen articulo, solo cambiar ‘echo “rsync ya se está ejecutando: $(cat $LCK)”‘ por ‘echo “rsync ya se está ejecutando: $(cat $LOCK)”‘

    Saludos

  2. Es un comando muy útil para tener en cuenta. Yo lo usé en un script que publiqué en un post, para borrar algunos archivos que creaba el script cuando éste era detenido.

Dejar una respuesta