Ich habe ein einfaches Bash-Skript mit einer Schleife zum Drucken des Datums und Ping an einen entfernten Rechner geschrieben:
#!/bin/bash
while true; do
# *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
echo -e "\n*** DATE:" `date` " ***";
echo "********************************************"
ping -c5 $1;
done
Wenn ich es von einem Terminal aus starte, kann ich es nicht mit Strg+C stoppen .
Es scheint, dass es ^C sendet zum Terminal, aber das Skript stoppt nicht.
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
Egal wie oft ich es drücke oder wie schnell ich es mache. Ich kann es nicht verhindern.
Machen Sie den Test und realisieren Sie es selbst.
Als Nebenlösung stoppe ich es mit Strg+Z , das stoppt es und dann kill %1
.
Was genau passiert hier mit ^C ?
Akzeptierte Antwort:
Was passiert ist, dass beide bash
und ping
Erhalte das SIGINT (bash
nicht sein interaktiv, beide ping
und bash
in derselben Prozessgruppe ausführen, die von der interaktiven Shell, von der aus Sie dieses Skript ausgeführt haben, erstellt und als Vordergrundprozessgruppe des Terminals festgelegt wurde).
Allerdings bash
verarbeitet dieses SIGINT asynchron, erst nachdem der aktuell ausgeführte Befehl beendet wurde. bash
wird nur beendet, wenn dieses SIGINT empfangen wird, wenn der aktuell ausgeführte Befehl durch ein SIGINT stirbt (d. h. sein Beendigungsstatus zeigt an, dass er durch SIGINT beendet wurde).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Oben bash
, sh
und sleep
erhalte SIGINT, wenn ich Strg-C drücke, aber sh
wird normalerweise mit einem Exit-Code 0 beendet, also bash
ignoriert das SIGINT, weshalb wir „hier“ sehen.
ping
, zumindest die von iputils, verhält sich so. Wenn es unterbrochen wird, druckt es Statistiken und beendet sich mit einem 0- oder 1-Ausgangsstatus, je nachdem, ob seine Pings beantwortet wurden oder nicht. Also, wenn Sie Strg-C drücken, während Sie ping
läuft, bash
stellt fest, dass Sie Ctrl-C
gedrückt haben in seinen SIGINT-Handlern, aber seit ping
beendet sich normal, bash
wird nicht beendet.
Wenn Sie ein sleep 1
hinzufügen in dieser Schleife und drücken Sie Ctrl-C
während sleep
läuft, weil sleep
keinen speziellen Handler auf SIGINT hat, stirbt es und meldet sich bei bash
dass es an einem SIGINT gestorben ist, und in diesem Fall bash
wird beendet (es wird sich tatsächlich mit SIGINT töten, um die Unterbrechung seinem Elternteil zu melden).
Warum bash
sich so verhält, bin ich mir nicht sicher, und ich stelle fest, dass das Verhalten nicht immer deterministisch ist. Ich habe gerade die Frage auf der bash
gestellt Entwicklungs-Mailingliste (Aktualisieren :@Jilles hat jetzt den Grund in seiner Antwort festgenagelt).
Die einzige andere Shell, die ich gefunden habe, die sich ähnlich verhält, ist ksh93 (Update, wie von @Jilles erwähnt, ebenso wie FreeBSD sh
). Dort scheint SIGINT schlichtweg ignoriert zu werden. Und ksh93
wird beendet, wenn ein Befehl von SIGINT beendet wird.
Sie erhalten dasselbe Verhalten wie bash
oben, sondern auch:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Gibt „test“ nicht aus. Das heißt, es wird beendet (indem es sich dort mit SIGINT tötet), wenn der Befehl, auf den es gewartet hat, von SIGINT stirbt, selbst wenn es selbst dieses SIGINT nicht erhalten hat.
Eine Problemumgehung wäre das Hinzufügen von a:
trap 'exit 130' INT
Am Anfang des Skripts, um bash
zu erzwingen zu beenden, wenn ein SIGINT empfangen wird (beachten Sie, dass SIGINT in keinem Fall synchron verarbeitet wird, sondern erst, nachdem der aktuell ausgeführte Befehl beendet wurde).
Idealerweise möchten wir unseren Eltern mitteilen, dass wir an einem SIGINT gestorben sind (so dass, wenn es ein weiterer bash
ist Skript zum Beispiel, das bash
Skript wird ebenfalls unterbrochen). Ausführen eines exit 130
ist nicht dasselbe wie das Sterben von SIGINT (obwohl einige Shells $?
setzen auf denselben Wert für beide Fälle), wird jedoch häufig verwendet, um einen Todesfall durch SIGINT zu melden (auf Systemen, bei denen SIGINT 2 ist, was am meisten ist).
Allerdings für bash
, ksh93
oder FreeBSD sh
, das geht nicht. Dieser 130-Exit-Status wird von SIGINT nicht als Tod betrachtet, und ein übergeordnetes Skript würde nicht dort abbrechen.
Eine möglicherweise bessere Alternative wäre also, uns mit SIGINT umzubringen, nachdem wir SIGINT:
erhalten habentrap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT