Bash ne capte pas les interruptions pendant les instructions d'exécution rsync/subshell
Question
Contexte:
J'ai un script bash qui contient un sous-shell et un piège pour le pseudosignal EXIT, et il ne capte pas correctement les interruptions lors d'un rsync
.Voici un exemple :
#!/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
L'idée du script est la suivante :la plupart de la logique importante s'exécute dans un sous-shell qui passe par tee
et dans un fichier journal, donc je n'ai pas à le faire tee
chaque ligne de la logique principale pour que tout soit enregistré.Chaque fois que le sous-shell se termine ou que le script est arrêté pour une raison quelconque (le pseudosignal EXIT devrait capturer tous ces cas), le piège l'interceptera et exécutera le cleanup()
fonction, puis retirez le piège.Le rsync
et sleep
les commandes (le sleep n'est qu'un exemple) sont exécutées exec
pour empêcher la création de processus zombies si je tue le script parent pendant son exécution, et que chaque commande potentiellement longue est enveloppée dans son propre sous-shell de sorte que lorsque exec
se termine, cela ne mettra pas fin à tout le script.
Le problème:
Si j'interromps le script (via kill
ou CTRL+C) pendant l'exécution/sous-shell encapsulé sleep
commande, le piège fonctionne correctement, et je vois « Nettoyage ! » résonner et enregistré.Si j'interromps le script pendant le rsync
commande, je vois rsync
terminer et écrire rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
à l'écran, puis le script meurt ;pas de nettoyage, pas de piégeage.Pourquoi une interruption/un meurtre de rsync
déclencher le piège ?
J'ai essayé d'utiliser le --no-detach
switcher avec rsync, mais cela n'a rien changé.J'ai bash 4.1.2, rsync 3.0.6, centOS 6.2.
La solution
Que diriez-vous de simplement avoir toutes les sorties de Point X être redirigé vers vous sans avoir à le répéter partout et en désordre avec toutes les sous-coquilles et exécutions ... (espérons que je n'ai pas manqué quelque chose)
#!/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
Autres conseils
En plus de set -e
, je pense que tu veux set -E
:
S'il est défini, toute interruption sur ERR est héritée par les fonctions shell, les substitutions de commandes et les commandes exécutées dans un environnement sous-shell.Le piège ERR n'est normalement pas hérité dans de tels cas.
Alternativement, au lieu d'encapsuler vos commandes dans des sous-shell, utilisez des accolades qui vous donneront toujours la possibilité de rediriger les sorties des commandes mais les exécuteront dans le shell actuel.
L'interruption sera correctement interceptée si vous ajoutez INT au piège
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash capture correctement les interruptions.Cependant, cela ne répond pas à la question de savoir pourquoi le script se bloque à la sortie si sleep
est interrompu, ni pourquoi il ne se déclenche pas rsync
, mais fait fonctionner le script comme il est censé le faire.J'espère que cela t'aides.
Votre shell peut être configuré pour se fermer en cas d'erreur :
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
Si vous interrompez sleep
(^C), le sous-shell se fermera à cause de set -e
et imprimer woah
Dans le processus.
Aussi, légèrement sans rapport :ton trap - EXIT
est dans un sous-shell (explicitement), donc cela n'aura aucun effet après le retour de la fonction de nettoyage
Il ressort assez clairement de l'expérimentation que rsync
se comporte comme d'autres outils tels que ping
et n'hérite pas des signaux du parent Bash appelant.
Vous devez donc faire preuve d'un peu de créativité et faire quelque chose comme ce qui suit :
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
Maintenant, quand je le lance :
$ ./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
Et nous pouvons voir que le fichier a été entièrement transféré :
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
Comment ça fonctionne
L'astuce ici est que nous disons à Bash via set -m
pour désactiver les contrôles des tâches sur toutes les tâches en arrière-plan qu'il contient.Nous étudions ensuite le contexte rsync
puis exécuter un wait
commande qui attendra la dernière commande exécutée, rsync
, jusqu'à ce qu'il soit terminé.
Nous gardons ensuite l'intégralité du script avec le trap '' SIGINT SIGTERM EXIT
.