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.

Foi útil?

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.

Referências

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top