Bash non cattura gli interrupt durante le istruzioni exec rsync/subshell
Domanda
Contesto:
Ho uno script bash che contiene una subshell e una trappola per lo pseudosegnale EXIT e non intrappola correttamente gli interrupt durante un rsync
.Ecco un esempio:
#!/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'idea della sceneggiatura è questa:la maggior parte della logica importante viene eseguita in una subshell che viene convogliata tee
e in un file di registro, quindi non è necessario tee
ogni singola riga della logica principale per registrarla tutta.Ogni volta che la subshell termina, o lo script viene interrotto per qualsiasi motivo (lo pseudosegnale EXIT dovrebbe catturare tutti questi casi), la trappola lo intercetterà ed eseguirà il comando cleanup()
funzione, quindi rimuovere la trappola.IL rsync
E sleep
vengono eseguiti i comandi (lo sleep è solo un esempio). exec
per impedire la creazione di processi zombie se uccido lo script principale mentre sono in esecuzione e ogni comando potenzialmente di lunga esecuzione è racchiuso nella propria subshell in modo che quando exec
termina, non terminerà l'intero script.
Il problema:
Se interrompo lo script (via kill
o CTRL+C) durante il wrapper di exec/subshell sleep
Comando, la trap funziona correttamente e vedo "Pulizia!" fatto eco e registrato.Se interrompo la sceneggiatura durante il rsync
comando, vedo rsync
finisci e scrivi rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
sullo schermo, e poi la sceneggiatura muore;nessuna pulizia, nessuna cattura.Perché un'interruzione/uccisione di rsync
innescare la trappola?
Ho provato a usare il --no-detach
cambia con rsync, ma non ha cambiato nulla.Ho bash 4.1.2, rsync 3.0.6, centOS 6.2.
Soluzione
Che ne dici di avere tutta la produzione dal punto x essere reindirizzato a tee senza doverlo ripeterlo ovunque e pasticcio con tutti i sottoboschi e gli execs ... (spero di non perdere qualcosa)
#!/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
. Altri suggerimenti
Oltre a set -e
, penso che tu voglia set -E
:
.Se impostato, qualsiasi trap on err è ereditata da funzioni di shell, sostituzioni di comando e comandi eseguiti in un ambiente sub-shell.La trappola ERR non è normalmente ereditata in questi casi.
In alternativa, invece di avvolgere i comandi in sott'obberimenti Utilizzare bretelle ricci che ti darà ancora la possibilità di reindirizzare le uscite dei comandi, ma li eseguiranno nel guscio corrente.
L'interruzione sarà correttamente catturato se si aggiunge int alla trappola
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
.
Bash è intrappolare correttamente gli interrupt.Tuttavia, questo non è anwer la domanda, perché lo script trappole in uscita se sleep
è interrotto, né perché non si attiva su rsync
, ma rende lo script funzionante come dovrebbe.Spero che questo aiuti.
La tua shell potrebbe essere configurata per uscire in caso di errore:
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
Se interrompi sleep
(^C), la subshell uscirà a causa di set -e
e stampare woah
nel processo.
Inoltre, leggermente non correlato:tuo trap - EXIT
è in una subshell (esplicitamente), quindi non avrà effetto dopo il ritorno della funzione di pulizia
È abbastanza chiaro dalla sperimentazione che rsync
si comporta come altri strumenti come ping
e non ereditano segnali dal genitore bash chiamante.
Quindi devi ottenere un po 'creativo con questo e fare qualcosa come il seguente:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
.
Ora quando lo eseguo:
$ ./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
.
E possiamo vedere il file ha fatto il trasferimento completamente:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
.
Come funziona
Il trucco Qui stiamo dicendo a Bash tramite set -m
a disabilitare i controlli di lavoro su qualsiasi lavoro di sfondo al suo interno. Allora studiando il rsync
e quindi eseguendo un comando wait
che attenderà il comando dell'ultimo funzionamento, rsync
, finché non è completo.
Allora cerchiamo l'intero script con il trap '' SIGINT SIGTERM EXIT
.