Bash no atrapa interrupciones durante declaraciones ejecutivas de rsync/subshell
Pregunta
Contexto:
Tengo un script bash que contiene un subshell y una trampa para la pseudoseñal EXIT, y no captura adecuadamente las interrupciones durante una rsync
.He aquí un ejemplo:
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT
(
#main script logic, including the following lines:
(exec sleep 10;);
(exec rsync --progress -av --delete $directory1 /var/tmp/$directory2;);
) | 2>&1 tee -a $logfile
trap - EXIT #just in case cleanup isn't called for some reason
La idea del guión es esta:la mayor parte de la lógica importante se ejecuta en una subcapa que se canaliza a través de tee
y a un archivo de registro, para no tener que hacerlo tee
cada línea de la lógica principal para registrarlo todo.Siempre que finalice el subshell, o el script se detenga por cualquier motivo (la pseudoseñal EXIT debería capturar todos estos casos), la trampa lo interceptará y ejecutará el cleanup()
función y luego retire la trampa.El rsync
y sleep
Los comandos (el sueño es solo un ejemplo) se ejecutan. exec
para evitar la creación de procesos zombies si elimino el script principal mientras se están ejecutando, y cada comando potencialmente de larga duración está envuelto en su propio subshell para que cuando exec
termina, no finalizará todo el script.
El problema:
Si interrumpo el guión (a través de kill
o CTRL+C) durante el exec/subshell ajustado sleep
Comando, la trampa funciona correctamente y veo "¡Limpieza!" resonado y registrado.Si interrumpo el guión durante el rsync
comando, ya veo rsync
terminar y escribir rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
a la pantalla, y luego el guión simplemente muere;sin limpieza, sin trampas.¿Por qué no interrumpir/matar a rsync
activar la trampa?
He intentado usar el --no-detach
cambie con rsync, pero no cambió nada.Tengo bash 4.1.2, rsync 3.0.6, centOS 6.2.
Solución
¿Qué tal si redirigimos toda la salida del punto X al tee sin tener que repetirla en todas partes y meternos con todos los sub-shells y ejecutivos...(espero no haberme perdido nada)
#!/bin/bash
logfile=/path/to/file;
directory1=/path/to/dir
directory2=/path/to/dir
exec > >(exec tee -a $logfile) 2>&1
cleanup () {
echo "Cleaning up!"
#do stuff
trap - EXIT
}
trap cleanup EXIT
sleep 10
rsync --progress -av --delete $directory1 /var/tmp/$directory2
Otros consejos
Además de set -e
, creo que quieres set -E
:
Si se establece, cualquier trampa en ERR es heredada por funciones de shell, sustituciones de comandos y comandos ejecutados en un entorno de subshell.La trampa ERR normalmente no se hereda en tales casos.
Alternativamente, en lugar de envolver sus comandos en subshells, use llaves que aún le darán la capacidad de redirigir las salidas de los comandos pero las ejecutarán en el shell actual.
La interrupción se detectará correctamente si agrega INT a la trampa
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash captura las interrupciones correctamente.Sin embargo, esto no responde a la pregunta de por qué el script se bloquea al salir si sleep
se interrumpe, ni por qué no se activa rsync
, pero hace que el script funcione como se supone que debe hacerlo.Espero que esto ayude.
Es posible que su shell esté configurado para salir en caso de error:
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
si interrumpes sleep
(^C) entonces la subcapa saldrá debido a set -e
e imprimir woah
en el proceso.
Además, un poco sin relación:su trap - EXIT
está en una subcapa (explícitamente), por lo que no tendrá efecto después de que regrese la función de limpieza
Está bastante claro a partir de la experimentación que rsync
Se comporta como otras herramientas como ping
y no hereda señales del padre Bash que llama.
Entonces debes ser un poco creativo con esto y hacer algo como lo siguiente:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
Ahora cuando lo ejecuto:
$ ./rsync.bash
X11 forwarding request failed
building file list ... done
LargeTestFile.500M
^C^C^C^C^C^C^C^C^C^C
sent 509984 bytes received 42 bytes 92732.00 bytes/sec
total size is 524288000 speedup is 1027.96
FIN
Y podemos ver que el archivo se transfirió por completo:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
Cómo funciona
El truco aquí es que le decimos a Bash a través de set -m
para deshabilitar los controles de trabajo en cualquier trabajo en segundo plano dentro de él.Luego estamos poniendo en segundo plano el rsync
y luego ejecutando un wait
comando que esperará el último comando de ejecución, rsync
, hasta completarlo.
Luego guardamos todo el guión con el trap '' SIGINT SIGTERM EXIT
.