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.
Genial! Gracias por compartir.
Buen articulo, solo cambiar ‘echo «rsync ya se está ejecutando: $(cat $LCK)»‘ por ‘echo «rsync ya se está ejecutando: $(cat $LOCK)»‘
Saludos
Un artículo muy interesante, sí señor! Este me lo guardo.
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.
Muy interesante, si señor.