O que acontece é que tanto bash
e ping
receber a SIGINT ( bash
sendo não interativa, tanto ping
e bash
executados no mesmo grupo de processos que foi criado e definido como grupo de processo de primeiro plano do terminal pelo shell interativo você executou o script a partir).
No entanto, bash
lida com o SIGINT de forma assíncrona, somente após a saída do comando em execução no momento. bash
só sai ao receber esse SIGINT se o comando atualmente em execução morrer de um SIGINT (ou seja, seu status de saída indica que ele foi morto pelo SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Acima, bash
, sh
e sleep
receber SIGINT quando eu pressionar Ctrl-C, mas sh
saídas normalmente com um código de saída 0, de modo bash
ignora a SIGINT, que é por isso que vemos "aqui".
ping
, pelo menos o de iputils, se comporta assim. Quando interrompido, ele imprime estatísticas e sai com um status de saída 0 ou 1, dependendo se seus pings foram ou não respondidos. Portanto, quando você pressiona Ctrl-C enquanto ping
está em execução, bash
observa que você pressionou Ctrl-C
seus manipuladores SIGINT, mas desde que ping
sai normalmente, bash
não sai.
Se você adicionar um sleep 1
nesse loop e pressionar Ctrl-C
enquanto sleep
estiver em execução, porque sleep
não possui um manipulador especial no SIGINT, ele morrerá e informará bash
que morreu de um SIGINT e, nesse caso bash
, sairá (ele realmente se matará com o SIGINT, de modo que para relatar a interrupção ao pai).
Quanto a por que bash
se comporta assim, não tenho certeza e observo que o comportamento nem sempre é determinístico. Acabei de fazer a pergunta na bash
lista de discussão de desenvolvimento ( atualização : @Jilles agora identificou o motivo em sua resposta ).
O único outro shell que descobri que se comporta de maneira semelhante é o ksh93 (o Update, como mencionado pelo @Jilles, o FreeBSDsh
). Lá, o SIGINT parece ser claramente ignorado. E ksh93
sai sempre que um comando é morto pelo SIGINT.
Você obtém o mesmo comportamento bash
acima, mas também:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Não gera "teste". Ou seja, ele sai (se matando com o SIGINT lá) se o comando que ele estava esperando morre do SIGINT, mesmo que ele próprio não tenha recebido esse SIGINT.
Uma solução alternativa seria adicionar um:
trap 'exit 130' INT
Na parte superior do script, para forçar bash
a saída ao receber um SIGINT (observe que, em qualquer caso, o SIGINT não será processado de forma síncrona, somente após a saída do comando em execução no momento).
Idealmente, gostaríamos de informar aos nossos pais que morremos de um SIGINT (para que, se for outro bash
script, por exemplo, esse bash
script também seja interrompido). Fazer um exit 130
não é o mesmo que morrer com o SIGINT (embora algumas conchas tenham o $?
mesmo valor nos dois casos), no entanto, é frequentemente usado para relatar uma morte pelo SIGINT (em sistemas onde o SIGINT é 2, o que é mais).
No entanto bash
, para o ksh93
FreeBSD sh
, isso não funciona. Esse status de saída 130 não é considerado como uma morte pelo SIGINT e um script pai não seria interrompido lá.
Portanto, uma alternativa possivelmente melhor seria nos matar com o SIGINT ao receber o SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Se o usuário digitar Ctrl + C durante a edição de um dos arquivos,vi
apenas exibirá uma mensagem. Parece razoável que o loop continue depois que o usuário terminar de editar o arquivo. (E sim, eu sei que você poderia dizervi *.txt; cp *.txt newdir
, estou apenas a apresentação dofor
circuito como um exemplo.)