Bash não captura interrupções durante instruções rsync/subshell exec
Pergunta
Contexto:
Eu tenho um script bash que contém um subshell e uma armadilha para o pseudosinal EXIT, e ele não está capturando interrupções corretamente durante um rsync
.Aqui está um exemplo:
#!/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
A ideia do roteiro é esta:a maior parte da lógica importante é executada em um subshell que é canalizado tee
e para um arquivo de log, então não preciso tee
cada linha da lógica principal para registrar tudo.Sempre que o subshell terminar ou o script for interrompido por qualquer motivo (o pseudosinal EXIT deve capturar todos esses casos), o trap irá interceptá-lo e executar o cleanup()
função e, em seguida, remova a armadilha.O rsync
e sleep
comandos (o sono é apenas um exemplo) são executados exec
para evitar a criação de processos zumbis se eu matar o script pai enquanto eles estão em execução, e cada comando potencialmente de longa execução é agrupado em seu próprio subshell para que quando exec
terminar, não encerrará todo o script.
O problema:
Se eu interromper o script (via kill
ou CTRL+C) durante o exec/subshell encapsulado sleep
Comando, a armadilha funciona corretamente e vejo "Limpando!" ecoou e registrado.Se eu interromper o script durante o rsync
comando, eu vejo rsync
terminar e escrever rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(544) [sender=3.0.6]
para a tela, e então o script simplesmente morre;sem limpeza, sem armadilhas.Por que uma interrupção/matança de rsync
acionar a armadilha?
Eu tentei usar o --no-detach
mude com rsync, mas não mudou nada.Tenho bash 4.1.2, rsync 3.0.6, centOS 6.2.
Solução
Que tal ter toda a saída do ponto X redirecionada para tee sem ter que repeti-la em todos os lugares e mexer com todos os sub-shells e execs ...(espero não ter 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
Outras dicas
Além de set -e
, acho que você quer set -E
:
Se definido, qualquer interceptação em ERR é herdada por funções shell, substituições de comandos e comandos executados em um ambiente subshell.A armadilha ERR normalmente não é herdada nesses casos.
Alternativamente, em vez de agrupar seus comandos em subshells, use chaves que ainda lhe darão a capacidade de redirecionar as saídas dos comandos, mas os executarão no shell atual.
A interrupção será capturada corretamente se você adicionar INT à armadilha
trap '{
(cleanup;) | 2>&1 tee -a $logfile
}' EXIT INT
Bash está capturando interrupções corretamente.No entanto, isso não responde à pergunta: por que o script é interceptado na saída se sleep
é interrompido, nem por que não dispara em rsync
, mas faz com que o script funcione como deveria.Espero que isto ajude.
Seu shell pode estar configurado para sair em caso de erro:
bash # enter subshell
set -e
trap "echo woah" EXIT
sleep 4
Se você interromper sleep
(^C), então o subshell será encerrado devido a set -e
e imprimir woah
no processo.
Além disso, um pouco não relacionado:seu trap - EXIT
está em um subshell (explicitamente), portanto não terá efeito após o retorno da função de limpeza
É bastante claro pela experimentação que rsync
se comporta como outras ferramentas, como ping
e não herda sinais do pai Bash chamador.
Então você precisa ser um pouco criativo com isso e fazer algo como o seguinte:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M root@host.mydom.com:/tmp/. &
wait
echo FIN
Agora, quando eu executo:
$ ./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 podemos ver que o arquivo foi totalmente transferido:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
Como funciona
O truque aqui é que estamos contando ao Bash via set -m
para desabilitar controles de trabalho em quaisquer trabalhos em segundo plano dentro dele.Estamos então em segundo plano rsync
e então executando um wait
comando que irá aguardar no último comando de execução, rsync
, até que esteja completo.
Em seguida, guardamos todo o script com o trap '' SIGINT SIGTERM EXIT
.