Atraso na finalização do script

4

Eu tenho um script como este (escrito para /bin/sh no OpenBSD) que primeiro atualiza uma cópia local de alguns repositórios CVS usando rsync , e então atualiza a versão com check-out destes no meu computador. O script é executado como root através do doas do OpenBSD (a sudo "equivalente" no OpenBSD):

#!/bin/sh

int_handler () {
    echo 'Wait...' >&2
    quit=1
}

quit=0
trap int_handler INT

for r in CVSROOT src xenocara ports; do
    su cvsuser -c 'rsync --archive --itemize-changes --delete --omit-dir-times \
        "rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
        "/extra/cvs/$r/"'
done

trap - INT

[ "$quit" -eq 1 ] && exit

# rest of script updates checked-out CVS repositories from local copy

A idéia é que eu pressione Ctrl + C enquanto o loop rsync está rodando para depois pular a segunda parte do script (que nem sempre quero executar, dependendo do que foi atualizado nos repositórios) sem interromper o loop e seus processos su + rsync .

Isso não funciona e o processo rsync recebe o sinal e termina (e a próxima iteração do loop for continua). rsync diz (quando pressiona Ctrl + C várias vezes até o script terminar):

^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(504) [receiver=3.1.3]
Wait...
rsync: [receiver] write error: Broken pipe (32)
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(1633) [sender=3.1.3]
^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(504) [receiver=3.1.3
]

rsync: [receiver] write error: Broken pipe (32)
Wait...
^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]
rsync error: received SIGUSR1 (code 19) at main.c(1440) [receiver=3.1.3]
Wait...

De acordo com uma resposta à pergunta " Como fazer ctrl + c / not / interromper o loop while? ", pelo menos quando executando bash , deve-se ser capaz de ignorar o sinal INT , o que fará com que os processos filhos também ignorem esse sinal. Isso não é ideal, mas eu ficaria feliz em tentar.

Então, segundo tente à luz disso:

#!/usr/local/bin/bash

trap '' INT

for r in CVSROOT src xenocara ports; do
    su cvsuser -c 'rsync --archive --itemize-changes --delete --omit-dir-times \
        "rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
        "/extra/cvs/$r/"'
done

trap - INT

read -p 'Press enter or interrupt...' junk

# rest of script updates checked-out CVS repositories from local copy

Isso também permite que o processo rsync seja interrompido por Ctrl + C .

Ok, então talvez seja o su que redefine a máscara de sinal? Uma terceira tentativa:

#!/usr/local/bin/bash

trap '' INT

for r in CVSROOT src xenocara ports; do
    su -s /usr/local/bin/bash cvsuser -c \
        'trap "" INT; rsync --archive --itemize-changes --delete --omit-dir-times \
        "rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
        "/extra/cvs/$r/"'
done

trap - INT

read -p 'Press enter or interrupt...' junk

# rest of script updates checked-out CVS repositories from local copy

Não, não há alegria. O processo rsync é ainda interrompível da mesma maneira.

Existe uma maneira de fazer o script funcionar da maneira que eu (inicialmente) pretendia?

bash na minha máquina é o release 4.4.23. O /bin/sh é a variante pdksh nativa no OpenBSD rodando no modo POSIX.

    
por Kusalananda 16.06.2018 / 10:58

4 respostas

5

Então, como você não quer realmente uma interrupção, que tal desabilitá-la no nível do terminal, com stty intr "" ? Isso faria com que o ^C aparecesse no buffer de entrada como qualquer caractere comum, e podemos lê-lo a partir daí.

Isso funciona para mim no Linux:

#!/bin/bash

t=$(stty -g)
stty intr ""
echo please hold
sleep 5
stty "$t"

# short timeout, just check if there was any input earlier
read -n1 -t0.1 a
[[ "$a" = $'
#!/bin/bash

t=$(stty -g)
stty intr ""
echo please hold
sleep 5
stty "$t"

# short timeout, just check if there was any input earlier
read -n1 -t0.1 a
[[ "$a" = $'%pre%3' ]] && echo "you hit ^C"
3' ]] && echo "you hit ^C"

(Eu não posso testar no OpenBSD, mas suponho que a configuração do terminal seja padrão.)

    
por 16.06.2018 / 13:06
3

rsync instala um manipulador no SIGINT mesmo se o SIGINT for ignorado no início. Então, se você não quiser que o ^C pare o rsync (eu odiaria a mim mesmo como odeio quando o botão de emergência não funciona), você teria que ter certeza que o SIGINT não foi entregue para rsync . / p>

Ou diga ao driver do terminal para não enviar um SIGINT para o grupo de processos em primeiro plano, em ^C , como na resposta do @ ikkachu , ou coloque o processo em execução Comando rsync fora do grupo de processos em primeiro plano.

Como tudo relacionado ao manuseio de sinais, se isso funciona ou não varia de shell para shell (e sua versão às vezes).

Sua melhor aposta é ficar com uma implementação conhecida que você sabe que funciona e espero que ainda funcione em versões mais recentes dentro de alguns anos. Como com zsh :

#! /usr/bin/env zsh

# enable job control (run pipelines in different process groups)
set -o monitor

interrupted() false
trap 'echo Wait...; interrupted() true' INT

# run in background so it won't get the SIGINT upon ^C
# as it won't be in the foreground process group. Note that with
# version 5.1.1 at least, running it without & will also run in
# in a new process group, and not make it the foreground process
# group of the terminal as the shell is not interactive. However
# the trap would be run asynchronously.
rsync... &

while (($#jobstates)) {wait}

interrupted && exit

echo rest of the script
    
por 16.06.2018 / 17:01
2

No Linux, você pode usar o comando setsid para executar o rsync em uma sessão separada. O OpenBSD não parece ter isso como um comando, embora a chamada do sistema exista. Você poderia escrever sua própria versão em C do comando, ou tentar este perl se você o tiver:

#!/usr/bin/perl
use POSIX qw(setsid);
POSIX::setsid();
exec @ARGV;

Se você nomear este script mysetsid , você o usaria como um prefixo para o comando su , por exemplo: ./mysetsid su cvsuser ... na primeira versão do seu script. Note que a interrupção só é vista pela função do manipulador quando o comando rsync atual termina.

    
por 16.06.2018 / 14:06
1

Implementação de Bash

É bem claro, por meio da experimentação, que rsync se comporta como outras ferramentas, como ping , e não herda sinais do pai Bash chamador.

Então você tem que ter um pouco de criatividade com isso e fazer algo como o seguinte:

$ cat rsync.bash
#!/bin/sh

 set -m
 trap '' SIGINT SIGTERM EXIT
 rsync -avz LargeTestFile.500M [email protected]:/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 informando ao Bash via set -m para desativar os controles de trabalho em quaisquer trabalhos em segundo plano dentro dele. Em seguida, estamos fazendo o background rsync e, em seguida, executando o comando wait , que aguardará no último comando de execução, rsync , até que esteja concluído.

Em seguida, guardamos todo o script com o trap '' SIGINT SIGTERM EXIT .

Referências

por 10.07.2018 / 04:05