Haz tus scripts de bash más robustos con traps

Ícono Terminal

Imagina que tienes un script de bash que se ejecute cada cierto tiempo 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 la tarea en un estado inconsistente.

trap es una manera sencilla y efectiva de controlar la salida de 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 y 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 -av foo bar
  rm $LOCK
else
   echo "rsync ya se está ejecutando"
fi

En español plano, el script anterior comprueba si existe el archivo /var/run/rsync.lock y si este no existe, lo crea y posteriormente ejecuta el comando correspondiente; por último elimina el archivo lock al terminar la ejecución de los comandos. Si existe el archivo, el script simplemente envía un mensaje al usuario indicándole que ya el comando se está ejecutando.

Sin embargo, pudiera ocurrir, por una situación inesperada, que el archivo lock no se elimine pudiendo dejar 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 -av 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 o EXITel script se detiene y elimina el archivo lock.

Vale la pena decir que pudiera darse una situación de competencia 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 el cual 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 -av foo bar
  rm -f $LOCK
  trap - INT TERM EXIT
  else
    echo "rsync ya se está ejecutando: $(cat $LOCK)"
fi

La particularidad de este último es que se usa como ya se 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.

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en una estrella para puntuar!

Promedio de puntuación 5 / 5. Recuento de votos: 2

Hasta ahora, ¡no hay votos!. Sé el primero en puntuar este contenido.

Sé el primero en comentar

Dejar una contestacion

Tu dirección de correo electrónico no será publicada.


*