Eu tenho o seguinte roteiro:
suspense_cleanup () {
echo "Suspense clean up..."
}
int_cleanup () {
echo "Int clean up..."
exit 0
}
trap 'suspense_cleanup' SIGTSTP
trap 'int_cleanup' SIGINT
sleep 600
Se eu executá-lo e pressionar Ctrl-C
, Int clean up...
show e ele sai.
Mas se eu apertar Ctrl-Z
, os ^Z
caracteres são exibidos na tela e depois trava.
Como posso:
- Execute algum código de limpeza em
Ctrl-Z
, talvez até ecoando algo, e - prosseguir com a suspensão depois?
Lendo aleatoriamente a documentação do glibc, encontrei isto:
Os aplicativos que desativam a interpretação normal do caractere SUSP devem fornecer algum outro mecanismo para o usuário interromper o trabalho. Quando o usuário invoca esse mecanismo, o programa deve enviar um sinal SIGTSTP para o grupo de processos do processo, não apenas para o próprio processo.
Mas não tenho certeza se isso se aplica aqui e, de qualquer forma, não parece funcionar.
Contexto: Estou tentando fazer um shell script interativo que suporte todos os recursos relacionados ao suspense que o Vim/Neovim suporta. Nomeadamente:
- Capacidade de suspender programaticamente (com
:suspend
, em vez de apenas permitir que o usuário pressione Ctrl-z) - Capacidade de executar uma ação antes de suspender (salvar automaticamente no Vim)
- Capacidade de executar uma ação antes de retomar (VimResume no NeoVim)
Edit: Mudar sleep 600
para for x in {1..100}; do sleep 6; done
também não funciona.
Editar 2: funciona ao substituir sleep 600
por sleep 600 & wait
. Não tenho certeza de por que ou como isso funciona, ou quais são as limitações de algo assim.
A manipulação de sinais no Linux e outros sistemas semelhantes ao UNIX é um assunto muito complexo com muitos atores em jogo: driver de terminal do kernel, relação de processo pai -> filho, grupos de processos, terminal de controle, manipulação de shell de sinais com controle de trabalho ativado/desativado, sinal manipuladores em processos individuais e possivelmente mais.
Primeiro, as combinações de teclas Control- C, Control- Znão são manipuladas pelo shell, mas pelo kernel. Você pode ver as definições padrão com
stty -a
:Aqui vemos
intr = ^C
esusp = ^Z
. stty, por sua vez, obtém essas informações do kernel usando TCGETS ioctl syscall :As combinações de teclas padrão são definidas no código do kernel do Linux em
As ações padrão também são definidas:
O sinal finalmente vai para __kill_pgrp_info() que diz:
Isso é importante para nossa história - o sinal gerado com Control- Ce Control- Zé enviado para o grupo de processos de primeiro plano criado pelo shell interativo pai cujo líder é um script recém-executado. O script e seus filhos pertencem a um grupo.
Portanto, conforme observado corretamente nos comentários do usuário Kamil Maciorowski , quando você envia Control- Zapós iniciar seu script, o sinal SIGTSTP é recebido pelo script e
sleep
porque quando um sinal é enviado para um grupo, ele é recebido por todos os processos do grupo. Seria fácil ver se você removeu as armadilhas do seu código para que ficasse assim (BTW, sempre adicione um https://en.wikipedia.org/wiki/shebang_(unix) , não está definido o que deve acontecer se houver não é shebang))Execute-o (eu o chamei de sigtstp.sh) e pare:
ja
é o meu nome de usuário, o seu será diferente, os PIDs também serão diferentes, mas o que importa é que ambos os processos estão parados, conforme indicado pela letra 'T'. Deman ps
:Isso significa que ambos os processos obtiveram o sinal SIGTSTP. Agora, se ambos os processos, incluindo sigstop.sh, obtêm sinal, por que
suspense_cleanup()
o manipulador de sinal não é executado? O Bash não o executa atésleep 600
terminar. É um requisito imposto pelo POSIX :(observe que no mundo do código aberto e no padrão geral de TI é apenas uma coleção de dicas e não há exigência legal para forçar ninguém a segui-las). Não ajudaria se você dormisse menos, digamos 3 segundos, porque
sleep
o processo seria interrompido de qualquer maneira, para que nunca fosse concluído. Parasuspense_cleanup()
ser chamado imediatamente, temos que executá-lo em segundo plano e executarwait
conforme também explicado no link POSIX acima:Execute-o e pare-o:
Observe que ambos
sleep 600
esigtstp.sh
agora se foram:Está claro por que sigtstp.sh se foi -
wait
foi interrompido pelo sinal, é a última linha do script, então ele sai. É ainda mais surpreendente quando você percebe que, se enviar SIGINT, o sono ainda será executado mesmo após a morte de sigtstp.sh:Mas, devido à morte de seu pai, ele seria adotado pelo init:
A razão para isso é quando o shell executa um filho em segundo plano, ele desativa o manipulador SIGINT padrão, que deve encerrar o processo (signal(7)) nele]( https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02 .html ):
Algumas referências SO: https://stackoverflow.com/questions/46061694/bash-why-cant-i-set-a-trap-for-sigint-in-a-background-shell/46061734#46061734 , https:// stackoverflow.com/questions/45106725/why-do-shells-ignore-sigint-and-sigquit-in-backgrounded-processes . Se você quiser matar todas as crianças após receber o SIGINT, deverá fazê-lo manualmente no manipulador de armadilhas. Observe, no entanto, que SIGINT ainda é entregue a todos os filhos, mas apenas ignorado - se você não usou sleep, mas um comando que instala seu próprio manipulador SIGINT se fosse executado (tente tcpdump, por exemplo)! O manual da Glibc diz:
Mas por que o sono está morto depois de enviar o SIGTSTP para ele se não o matamos nós mesmos e o SIGTSTP deve apenas pará-lo, não matá-lo? Todos os processos parados pertencentes ao grupo de processos órfãos obtêm SIGHUP do kernel :
SIGHUP encerra o processo se nenhum manipulador personalizado para ele foi instalado (signal(7)):
(observe que se você rodar sleep sob strace as coisas ficariam ainda mais complexas...).
OK, então que tal voltar à sua pergunta original:
A forma que eu faria é:
Now
suspense_cleanup()
será chamado antes de parar o processo:E você pode dormir menos, digamos 10 segundos e ver que o script sairia se o sono terminasse:
Executá-lo:
Parece que o controle do trabalho Bash interfere nos caracteres especiais tty normais. O SIGTSTP pode ser enviado para o Bash, mas não para o processo que está sendo executado.
De https://www.gnu.org/software/bash/manual/bash.html#Job-Control-Basics :
Continuar (Ctrl-Q, chamado
start
emstty
) não funciona no controle de trabalho Bash: você precisafg
oubg
o processo para destravá-lo.Ao ler
man 7 signal
, ouhttps://www.man7.org/linux/man-pages/man7/signal.7.html
, percebe-se que:Então, você não pode.