Napsal jsem jednoduchý bash skript se smyčkou pro tisk data a ping na vzdálený počítač:
#!/bin/bash
while true; do
# *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
echo -e "\n*** DATE:" `date` " ***";
echo "********************************************"
ping -c5 $1;
done
Když to spouštím z terminálu, nejsem schopen to zastavit pomocí Ctrl+C .
Zdá se, že odesílá ^C do terminálu, ale skript se nezastaví.
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
Bez ohledu na to, kolikrát to stisknu nebo jak rychle to udělám. Nejsem schopen to zastavit.
Udělejte si test a uvědomte si to sami.
Jako vedlejší řešení to zastavím pomocí Ctrl+Z , to jej zastaví a poté kill %1
.
Co se přesně děje s ^C ?
Přijatá odpověď:
Stane se, že oba bash
a ping
přijímat SIGINT (bash
být ne interaktivní, oba ping
a bash
spustit ve stejné skupině procesů, která byla vytvořena a nastavena jako skupina procesů v popředí terminálu interaktivním prostředím, ze kterého jste spustili skript).
Nicméně bash
zpracovává tento SIGINT asynchronně, pouze po ukončení aktuálně spuštěného příkazu. bash
ukončí se pouze po přijetí tohoto SIGINT, pokud aktuálně spuštěný příkaz zanikne na SIGINT (tj. jeho stav ukončení indikuje, že byl zabit SIGINTem).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Nahoře bash
, sh
a sleep
přijímat SIGINT, když stisknu Ctrl-C, ale sh
ukončí se normálně s výstupním kódem 0, takže bash
ignoruje SIGINT, což je důvod, proč vidíme „zde“.
ping
, alespoň ten od iputils, se tak chová. Při přerušení vytiskne statistiku a ukončí se se stavem ukončení 0 nebo 1 v závislosti na tom, zda byly jeho pingy zodpovězeny nebo ne. Když tedy stisknete Ctrl-C při ping
běží, bash
poznamenává, že jste stiskli Ctrl-C
v jeho ovladačích SIGINT, ale od ping
ukončí se normálně, bash
neukončí se.
Pokud přidáte sleep 1
v této smyčce a stiskněte Ctrl-C
když sleep
běží, protože sleep
nemá na SIGINTu žádný speciální handler, zemře a ohlásí se bash
že zemřel na SIGINT, a v tom případě na bash
ukončí se (ve skutečnosti se sám zabije pomocí SIGINT, aby ohlásil přerušení svému rodiči).
Proč bash
chová se tak, nejsem si jistý a podotýkám, že chování není vždy deterministické. Právě jsem položil otázku na bash
vývojový e-mailový seznam (Aktualizace :@Jilles nyní ve své odpovědi uvedl důvod).
Jediný další shell, který jsem našel a který se chová podobně, je ksh93 (aktualizace, jak zmínil @Jilles, stejně jako FreeBSD sh
). Zdá se, že SIGINT je zjevně ignorován. A ksh93
ukončí se vždy, když je příkaz SIGINT ukončen.
Získáte stejné chování jako bash
výše, ale také:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Nevydává „test“. To znamená, že odejde (zabitím se tam pomocí SIGINT), pokud příkaz, na který čekal, zemře na SIGINT, i když sám tento SIGINT neobdržel.
Řešením by bylo přidat:
trap 'exit 130' INT
V horní části skriptu vynutit bash
pro ukončení po přijetí SIGINT (všimněte si, že SIGINT nebude v žádném případě zpracován synchronně, pouze po ukončení aktuálně spuštěného příkazu).
V ideálním případě bychom chtěli nahlásit rodičům, že jsme zemřeli na SIGINT (takže pokud je to další bash
skript například, že bash
skript je také přerušen). Provedením exit 130
není totéž jako umírání SIGINTem (ačkoli některé shelly nastaví $?
na stejnou hodnotu pro oba případy), ale často se používá k hlášení úmrtí pomocí SIGINT (na systémech, kde SIGINT je 2, což je nejvíce).
Nicméně pro bash
, ksh93
nebo FreeBSD sh
, to nejde. Tento stav ukončení 130 není SIGINTem považován za smrt a nadřazený skript by nebyl tam přerušit.
Takže možná lepší alternativou by bylo zabít se pomocí SIGINT po obdržení SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT