Bash fängt Interrupts während rsync / subshell exec-Anweisungen nicht ab
Frage
Kontext:
Ich habe ein Bash-Skript, das eine Subshell und einen Trap für das EXIT-Pseudosignal enthält, und es fängt Interrupts während eines nicht richtig ab rsync
.Hier ist ein Beispiel:
#!/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
Die Idee des Skripts ist dies:der größte Teil der wichtigen Logik läuft in einer Subshell, die durchgeleitet wird tee
und zu einer Logdatei, damit ich nicht muss tee
jede einzelne Zeile der Hauptlogik, um alles zu protokollieren.Immer wenn die Subshell endet oder das Skript aus irgendeinem Grund gestoppt wird (das EXIT-Pseudosignal sollte alle diese Fälle erfassen), fängt der Trap es ab und führt das aus cleanup()
funktion, und entfernen sie dann die falle.Der rsync
und sleep
befehle (der sleep ist nur ein Beispiel) werden durchlaufen exec
um die Erstellung von Zombie-Prozessen zu verhindern, wenn ich das übergeordnete Skript beende, während sie ausgeführt werden, und jeder potenziell lang laufende Befehl in eine eigene Subshell eingeschlossen wird, damit wann exec
beendet wird, wird das gesamte Skript nicht beendet.
Problem:
Wenn ich das Skript unterbreche (via kill
oder STRG + C) während der exec / Subshell gewickelt sleep
befehl, die Falle funktioniert einwandfrei, und ich sehe "Aufräumen!" echo und protokolliert.Wenn ich das Skript während des unterbreche rsync
befehl, ich verstehe rsync
ende und schreibe rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
auf den Bildschirm, und dann stirbt das Skript einfach;kein Aufräumen, kein Einfangen.Warum unterbricht / tötet man nicht rsync
die Falle auslösen?
Ich habe versucht, das zu verwenden --no-detach
wechseln Sie mit rsync, aber es hat nichts geändert.Ich habe Bash 4.1.2, rsync 3.0.6, CentOS 6.2.
Lösung
Wie wäre es, wenn einfach die gesamte Ausgabe von Punkt X zu tee umgeleitet wird, ohne sie überall wiederholen und mit allen Sub-Shells und Execs herumspielen zu müssen...(hoffe, ich habe nichts verpasst)
#!/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
Andere Tipps
Neben set -e
, Ich denke du willst set -E
:
Wenn gesetzt, wird jede Falle bei FEHLER von Shell‐Funktionen, Befehlsersetzungen und Befehlen geerbt, die in einer Sub-Shell-Umgebung ausgeführt werden.Die Fehlerfalle wird in solchen Fällen normalerweise nicht vererbt.
Alternativ können Sie Ihre Befehle nicht in Unterschalen einschließen, sondern geschweifte Klammern verwenden, die Ihnen weiterhin die Möglichkeit geben, Befehlsausgaben umzuleiten, sie jedoch in der aktuellen Shell auszuführen.
Die Unterbrechung wird ordnungsgemäß abgefangen, wenn Sie der Falle INT hinzufügen
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash fängt Interrupts korrekt ab.Dies beantwortet jedoch nicht die Frage, warum das Skript beim Beenden abfängt, wenn sleep
unterbrochen wird oder warum es nicht ausgelöst wird rsync
, aber lässt das Skript so funktionieren, wie es soll.Hoffe das hilft.
Ihre Shell ist möglicherweise so konfiguriert, dass sie bei einem Fehler beendet wird:
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
Wenn Sie unterbrechen sleep
(^ C) dann wird die Subshell aufgrund von beendet set -e
und drucken woah
im Prozess.
Auch etwas unabhängig:Ihrer trap - EXIT
befindet sich (explizit) in einer Subshell, sodass sie nach der Rückkehr der Bereinigungsfunktion keine Auswirkung hat
Es ist ziemlich klar aus Experimenten, dass rsync
verhält sich wie andere Tools wie ping
und erben Sie keine Signale vom aufrufenden Bash-Elternteil.
Sie müssen also ein wenig kreativ werden und so etwas wie das Folgende tun:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
Jetzt, wenn ich es laufen lasse:
$ ./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
Und wir können sehen, dass die Datei vollständig übertragen wurde:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
Wie es funktioniert
Der Trick hier ist, dass wir Bash via erzählen set -m
so deaktivieren Sie Jobsteuerelemente für alle darin enthaltenen Hintergrundjobs.Wir hintergründen dann die rsync
und dann läuft ein wait
befehl, der auf den letzten Ausführungsbefehl wartet, rsync
, bis es vollständig ist.
Wir bewachen dann das gesamte Skript mit dem trap '' SIGINT SIGTERM EXIT
.