Ho scritto un semplice script bash con un ciclo per la stampa della data e il ping su una macchina remota:
#!/bin/bash
while true; do
# *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
echo -e "\n*** DATE:" `date` " ***";
echo "********************************************"
ping -c5 $1;
done
Quando lo eseguo da un terminale non riesco a fermarlo con Ctrl+C .
Sembra che invii ^C al terminale, ma lo script non si ferma.
MacAir:~ tomas$ ping-tester.bash www.google.com
*** DATE: Thu Sep 17 23:58:42 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms
*** DATE: Thu Sep 17 23:58:48 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms
Non importa quante volte lo premo o quanto velocemente lo faccio. Non sono in grado di fermarlo.
Fai il test e realizzalo da solo.
Come soluzione secondaria, lo fermo con Ctrl+Z , questo lo interrompe e quindi kill %1
.
Cosa sta succedendo esattamente qui con ^C ?
Risposta accettata:
Quello che succede è che entrambi bash
e ping
ricevere il SIGINT (bash
essere non interattivo, entrambi ping
e bash
eseguito nello stesso gruppo di processi che è stato creato e impostato come gruppo di processi in primo piano del terminale dalla shell interattiva da cui è stato eseguito lo script).
Tuttavia, bash
gestisce SIGINT in modo asincrono, solo dopo che il comando attualmente in esecuzione è terminato. bash
esce solo alla ricezione di 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
ricevo SIGINT quando premo Ctrl-C, ma sh
esce normalmente con un codice di uscita 0, quindi bash
ignora il SIGINT, motivo per cui vediamo “qui”.
ping
, almeno quello di iputils, si comporta così. Quando viene interrotto, stampa le statistiche ed esce con uno stato di uscita 0 o 1 a seconda che i suoi ping abbiano risposto o meno. Quindi, quando premi Ctrl-C mentre ping
è in esecuzione, bash
nota che hai premuto Ctrl-C
nei suoi gestori SIGINT, ma da 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 riporterà a bash
che è morto di un SIGINT, e in quel caso bash
uscirà (si ucciderà effettivamente con SIGINT in modo da segnalare l'interruzione al suo genitore).
Sul perché bash
si comporta così, non ne sono sicuro e noto che il comportamento non è sempre deterministico. Ho appena posto la domanda su bash
mailing list di sviluppo (Aggiorna :@Jilles ha ora inchiodato il motivo nella sua risposta).
L'unica altra shell che ho trovato che si comporta in modo simile è ksh93 (Aggiorna, come menzionato da @Jilles, così come FreeBSD sh
). Lì, SIGINT sembra essere chiaramente ignorato. E ksh93
esce ogni volta che un comando viene ucciso da SIGINT.
Ottieni lo stesso comportamento di bash
sopra ma anche:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Non emette "test". Cioè, esce (uccidendosi con SIGINT lì) se il comando che stava aspettando muore di SIGINT, anche se lui stesso non ha ricevuto quel SIGINT.
Una soluzione sarebbe aggiungere un:
trap 'exit 130' INT
Nella parte superiore dello script per forzare bash
di uscire alla ricezione di un SIGINT (si noti che in ogni caso, SIGINT non verrà elaborato in modo sincrono, solo dopo l'uscita del comando attualmente in esecuzione).
Idealmente, vorremmo riferire al nostro genitore che siamo morti per un SIGINT (in modo che se fosse un altro bash
script per esempio, quel bash
anche lo script viene interrotto). Fare un exit 130
non è come morire di SIGINT (sebbene alcune shell imposteranno $?
allo stesso valore per entrambi i casi), tuttavia è spesso utilizzato per segnalare un decesso da SIGINT (su sistemi in cui SIGINT è 2 che è il massimo).
Tuttavia per bash
, ksh93
o FreeBSD sh
, non funziona. Quel 130 stato di uscita non è considerato una morte da SIGINT e uno script genitore non abortire 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