O que acontece é que tanto bashe pingreceber a SIGINT ( bashsendo não interativa, tanto pinge bashexecutados 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, bashlida com o SIGINT de forma assíncrona, somente após a saída do comando em execução no momento. bashsó 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, she sleepreceber SIGINT quando eu pressionar Ctrl-C, mas shsaídas normalmente com um código de saída 0, de modo bashignora 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 pingestá em execução, bashobserva que você pressionou Ctrl-Cseus manipuladores SIGINT, mas desde que pingsai normalmente, bashnão sai.
Se você adicionar um sleep 1nesse loop e pressionar Ctrl-Cenquanto sleepestiver em execução, porque sleepnão possui um manipulador especial no SIGINT, ele morrerá e informará bashque 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 bashse comporta assim, não tenho certeza e observo que o comportamento nem sempre é determinístico. Acabei de fazer a pergunta na bashlista 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 ksh93sai sempre que um comando é morto pelo SIGINT.
Você obtém o mesmo comportamento bashacima, 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 basha 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 bashscript, por exemplo, esse bashscript também seja interrompido). Fazer um exit 130nã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 ksh93FreeBSD 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,viapenas 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 doforcircuito como um exemplo.)