
Tengo una placa que ejecuta un script de "parche". El script del parche siempre se ejecuta en segundo plano y es un script de shell que ejecuta el siguiente pseudocódigo:
while true; do
# checks if a patch tar file exists and if yes then do patching
sleep 10
done
Este script está en /opt/patch.sh y se inició mediante el script de inicio de SystemV.
El problema es que cuando el script encuentra el tar, lo extrae y dentro hay un script de shell llamadoparche.shque es específico para el contenido del alquitrán.
Cuando el guión en/opt/patch.shencuentra el alquitrán y hace lo siguiente:
tar -xf /opt/update.tar -C /mnt/update
mv /mnt/update/patch.sh /opt/patch.sh
exec /opt/patch.sh
Se reemplaza por otro script y lo ejecuta desde la misma ubicación. ¿Puede ocurrir algún problema al hacer eso?
Respuesta1
Si el archivo se reemplaza al escribirlo en el lugar (el inodo permanece igual), cualquier proceso que lo tenga abierto verá los nuevos datos si/cuando lean el archivo. Si se reemplaza desvinculando el archivo antiguo y creando uno nuevo con el mismo nombre, el número de inodo cambia y cualquier proceso que mantenga el archivo abierto aún tendrá el mismo nombre.viejoarchivo.
mv
podría hacer cualquiera de las dos cosas, dependiendo de si el movimiento ocurre entre sistemas de archivos o no... Para asegurarse de obtener un archivo completamente nuevo, primero desvincule o cambie el nombre del original. Algo como esto:
mv /opt/patch.sh /opt/patch.sh.old # or rm
mv /mnt/update/patch.sh /opt/patch.sh
De esa manera, el shell en ejecución todavía tendría un identificador de archivo para los datos antiguos, incluso después del movimiento.
Dicho esto, hasta donde he probado, Bash lee todo el ciclo antes de ejecutarlo, por lo que cualquier cambio en el archivo subyacente no requeriría cambios en el script en ejecución siempre que la ejecución permanezca dentro del ciclo. (Tiene que leer todo el ciclo antes de ejecutarlo, ya que puede haber redirecciones que afecten a todo el ciclo al final).
Después de salir del ciclo, Bash mueve el puntero de lectura nuevamente y luego continúa leyendo el archivo de entrada desde la posición justo después de que finalizó el ciclo.
Cualquier función definida en el script también se carga en la memoria, por lo que poner la lógica principal del script en una función y llamarla solo al final haría que el script sea bastante seguro contra modificaciones en el archivo:
#!/bin/sh
main() {
do_stuff
exit
}
main
De todos modos, no es demasiado difícil probar qué sucede cuando se sobrescribe un script:
$ cat > old.sh <<'EOF'
#!/bin/bash
for i in 1 2 3 4 ; do
# rm old.sh
cat new.sh > old.sh
sleep 1
echo $i
done
echo will this be reached?
EOF
$ cat > new.sh <<'EOF'
#!/bin/bash
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
echo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
EOF
$ bash old.sh
Con lo rm old.sh
comentado, el guión se cambiará en el lugar. Sin el comentario, se creará un nuevo archivo. (Este ejemplo se basa en parte en new.sh
ser más grande que old.sh
, ya que si fuera más corto, la posición de lectura del shell estaría más allá del final del nuevo script después del ciclo).
Respuesta2
He tenido este problema antes y puedo confirmar que puede ser un problema. En mi caso, un script de regresión primero ejecuta un git pull y puede actualizarse después de que comienza a ejecutarse, lo que causa problemas.
El problema suele ser cuando el shell regresa y comprueba si hay más líneas para interpretar. Esto puede provocar un error incluso cuando el código deseado está dentro de un bucle. Para evitar esto, utilice la estructura enesta publicación.
Respuesta3
¿Un script autoejecutable y automodificable? Esa no es una buena idea.
Una mejor solución sería crear un demonio stub con funcionalidad mínima (es decir, responsable de instalar nuevas versiones del script esclavo e invocarlo a intervalos). Algo como... (no probado)
while true; do
# check if a patch tar file exists and if yes then do patching
if [ -f "$PATCH" ]; then
( cd /usr/local/mydaemon \
&& tar -xzf "$PATCH" \
&& rm -f "$PATCH" ) \
|| exit -1
fi
$SCRIPT
sleep 10
done