Quello che succede è che sia bash
e ping
ricevere il SIGINT ( bash
essendo non interattivi, sia ping
e bash
gestiscono nello stesso gruppo processo che è stato creato e insieme come gruppo processo in primo piano del terminale dalla shell interattiva è stato eseguito lo script da).
Tuttavia, bash
gestisce SIGINT in modo asincrono, solo dopo la chiusura del comando attualmente in esecuzione. bash
esce solo dopo aver ricevuto quel SIGINT se il comando attualmente in esecuzione muore di un SIGINT (cioè il suo stato di uscita indica che è stato ucciso da SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Sopra, bash
, sh
e sleep
ricevere SIGINT quando si preme Ctrl-C, ma sh
le uscite di solito con un codice di uscita 0, quindi bash
ignora il SIGINT, ed è per questo che vediamo "qui".
ping
, almeno quello di iputils, si comporta così. Se interrotto, stampa le statistiche ed esce con uno stato di uscita 0 o 1 a seconda che i ping abbiano o meno risposto. Quindi, quando premi Ctrl-C mentre ping
è in esecuzione, bash
nota che hai premuto Ctrl-C
nei suoi gestori SIGINT, ma poiché ping
esce normalmente, bash
non esce.
Se aggiungi un sleep 1
in quel ciclo e premi Ctrl-C
mentre sleep
è in esecuzione, perché sleep
non ha un gestore speciale su SIGINT, morirà e segnalerà bash
che è morto di un SIGINT, e in quel caso bash
uscirà (in realtà si ucciderà con SIGINT in modo che per segnalare l'interruzione al suo genitore).
Quanto al perché bash
si comporti così, non ne sono sicuro e noto che il comportamento non è sempre deterministico. Ho appena fatto la domanda sulla bash
mailing list di sviluppo ( Aggiornamento : @Jilles ha ora individuato il motivo nella sua risposta ).
L'unica altra shell che ho trovato che si comporta in modo simile è ksh93 (Update, come menzionato da @Jilles, così fa FreeBSDsh
). Lì, SIGINT sembra essere chiaramente ignorato. Ed ksh93
esce ogni volta che un comando viene ucciso da SIGINT.
Ottieni lo stesso comportamento di cui bash
sopra ma anche:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Non genera "test". Cioè, esce (uccidendosi con SIGINT lì) se il comando che stava aspettando muore di SIGINT, anche se esso stesso non ha ricevuto quel SIGINT.
Una soluzione sarebbe aggiungere un:
trap 'exit 130' INT
Nella parte superiore dello script per forzare bash
l'uscita al momento della ricezione di un SIGINT (notare che in ogni caso, SIGINT non verrà elaborato in modo sincrono, solo dopo la chiusura del comando attualmente in esecuzione).
Idealmente, vorremmo segnalare al nostro genitore che siamo morti per un SIGINT (quindi se si tratta di un altro bash
script, ad esempio, anche quello bash
script viene interrotto). Fare un exit 130
non è lo stesso che morire di SIGINT (anche se alcune shell imposteranno $?
lo stesso valore per entrambi i casi), tuttavia è spesso usato per segnalare un decesso di SIGINT (sui sistemi in cui SIGINT è 2 che è il più).
Tuttavia, per bash
, ksh93
o FreeBSD sh
, che non funziona. Quel 130 stato di uscita non è considerato un decesso da SIGINT e uno script genitore non si interromperà lì.
Quindi, un'alternativa forse migliore sarebbe ucciderci con SIGINT dopo aver ricevuto SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Se l'utente digita Ctrl + C durante la modifica di uno dei file,vi
visualizza solo un messaggio. Sembra ragionevole che il ciclo continui dopo che l'utente ha finito di modificare il file. (E sì, lo so che potresti dirlovi *.txt; cp *.txt newdir
; sto solo presentando ilfor
loop come esempio.)