Was passiert ist, dass beide bash
und ping
das SIGINT empfangen ( bash
da beide nicht interaktiv sind ping
und bash
in derselben Prozessgruppe ausgeführt werden, die von der interaktiven Shell, von der aus Sie das Skript ausgeführt haben, erstellt und als Vordergrundprozessgruppe des Terminals festgelegt wurde).
Allerdings bash
Griffe dass SIGINT asynchron, erst nach dem aktuell laufenden Befehl verlassen. bash
Beendet sich erst nach Erhalt dieses SIGINT, wenn der aktuell ausgeführte Befehl von einem SIGINT abbricht (dh sein Beendigungsstatus zeigt an, dass er von SIGINT getötet wurde).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Oben bash
, sh
und sleep
erhalten SIGINT , wenn ich Strg-C drücken, aber sh
Ausgänge normalerweise mit einem 0 Exit - Code, so bash
ignoriert die SIGINT, weshalb wir „hier“ zu sehen.
ping
zumindest der von iputils verhält sich so. Bei einer Unterbrechung werden Statistiken gedruckt und mit einem Ausgangsstatus von 0 oder 1 beendet, je nachdem, ob die Pings beantwortet wurden oder nicht. Wenn Sie also während der ping
Ausführung die Tastenkombination Strg-C bash
drücken, werden Ctrl-C
die SIGINT-Handler zwar gedrückt , da sie jedoch ping
normal beendet werden, bash
jedoch nicht beendet.
Wenn Sie ein Add sleep 1
in dieser Schleife und drücken Sie Ctrl-C
während sleep
ausgeführt wird , weil sleep
keine spezielle Handler auf SIGINT hat, wird er sterben und Bericht an , bash
dass es eines SIGINT gestorben, und in diesem Fall bash
beendet (es wird sich mit SIGINT tatsächlich töten , um die Unterbrechung dem Elternteil zu melden).
bash
Ich bin mir nicht sicher , warum sich das so verhält, und ich stelle fest, dass das Verhalten nicht immer deterministisch ist. Ich habe gerade die Frage auf der bash
Entwicklungs-Mailingliste gestellt ( Update : @Jilles hat jetzt den Grund in seiner Antwort festgehalten ).
Die einzige andere Shell, die sich ähnlich verhält, ist ksh93 (Update, wie von @Jilles erwähnt, auch FreeBSDsh
). Dort scheint SIGINT einfach ignoriert zu werden. Und wird beendet, ksh93
wenn ein Befehl von SIGINT beendet wird.
Sie erhalten das gleiche Verhalten wie bash
oben, aber auch:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Gibt "test" nicht aus. Das heißt, es beendet sich (indem es sich dort mit SIGINT umbringt), wenn der Befehl, auf den es gewartet hat, von SIGINT stirbt, auch wenn es selbst diesen SIGINT nicht erhalten hat.
Ein Workaround wäre, folgendes hinzuzufügen:
trap 'exit 130' INT
Oben im Skript, um bash
das Beenden beim Empfang eines SIGINT zu erzwingen (beachten Sie, dass SIGINT in jedem Fall nicht synchron verarbeitet wird, nur nachdem der aktuell ausgeführte Befehl beendet wurde).
Idealerweise möchten wir unseren Eltern mitteilen, dass wir an einem SIGINT gestorben sind (wenn es sich also beispielsweise um ein anderes bash
Skript handelt, wird dieses bash
Skript ebenfalls unterbrochen). Dies exit 130
ist nicht dasselbe wie das Sterben von SIGINT (obwohl einige Shells in $?
beiden Fällen den gleichen Wert annehmen), es wird jedoch häufig verwendet, um einen Tod von SIGINT zu melden (auf Systemen, bei denen SIGINT 2 ist, was am meisten ist).
Doch für bash
, ksh93
oder FreeBSD sh
, das nicht funktioniert. Dieser 130-Exit-Status wird von SIGINT nicht als Tod betrachtet, und ein übergeordnetes Skript würde dort nicht abgebrochen.
Eine möglicherweise bessere Alternative wäre es, sich mit SIGINT zu töten, wenn Sie SIGINT erhalten:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Wenn der Benutzer beim Bearbeiten einer der Dateien Strg + C eingibt, wirdvi
nur eine Meldung angezeigt. Es erscheint vernünftig, dass die Schleife fortgesetzt wird, nachdem der Benutzer die Bearbeitung der Datei abgeschlossen hat. (Und ja, ich weiß, dass Sie sagen könntenvi *.txt; cp *.txt newdir
; ich reiche nur diefor
Schleife als Beispiel ein.)vi
(gutvim
zumindest) nicht deaktiviert ttyisig
beim Bearbeiten (es nicht abviously , wenn Sie laufen:!cmd
aber, und das wäre sehr viel gilt in diesem Fall).ping
nach dem Empfang von SIGINT mit 0 beendet wird. Ich fand ein ähnliches Verhalten , wenn ein Bash - Skript enthältsudo
stattping
, abersudo
Ausgänge mit 1 nach SIGINT erhalten. unix.stackexchange.com/questions/479023/…Die Erklärung ist, dass bash WCE (Wait and Cooperative Exit) für SIGINT und SIGQUIT implementiert ( http://www.cons.org/cracauer/sigint.html) . Das heißt, wenn die Bash SIGINT oder SIGQUIT empfängt, während sie auf das Beenden eines Prozesses wartet, wartet sie, bis der Prozess beendet ist, und beendet sich selbst, wenn der Prozess bei diesem Signal beendet wurde. Dies stellt sicher, dass Programme, die SIGINT oder SIGQUIT in ihrer Benutzeroberfläche verwenden, wie erwartet funktionieren (wenn das Signal nicht zum Beenden des Programms geführt hat, wird das Skript normal fortgesetzt).
Ein Nachteil tritt bei Programmen auf, die SIGINT oder SIGQUIT abfangen, dann jedoch beenden, aber einen normalen exit () verwenden, anstatt das Signal erneut an sich selbst zu senden. Skripte, die solche Programme aufrufen, können möglicherweise nicht unterbrochen werden. Ich denke, die eigentliche Lösung gibt es in solchen Programmen wie Ping und Ping6.
Ein ähnliches Verhalten wird von ksh93 und FreeBSD's / bin / sh implementiert, aber nicht von den meisten anderen Shells.
quelle
exit(130)
beispielsweise, wenn Sie unterbrechenmksh -c 'sleep 10;:'
).Wie Sie vermuten, liegt dies daran, dass SIGINT an den untergeordneten Prozess gesendet wird und die Shell nach dem Beenden dieses Prozesses fortgesetzt wird.
Um dies besser zu handhaben, können Sie den Beendigungsstatus der ausgeführten Befehle überprüfen. Der Unix-Rückkehrcode codiert sowohl die Methode, mit der ein Prozess beendet wurde (Systemaufruf oder Signal), als auch den Wert, an den
exit()
der Prozess übergeben wurde, oder das Signal, mit dem der Prozess beendet wurde. Dies ist alles ziemlich kompliziert, aber die schnellste Möglichkeit, es zu verwenden, besteht darin, zu wissen, dass ein Prozess, der durch ein Signal beendet wurde, einen Rückkehrcode ungleich Null aufweist. Wenn Sie also den Rückkehrcode in Ihrem Skript überprüfen, können Sie sich selbst beenden, wenn der untergeordnete Prozess beendet wurde, und so die Notwendigkeit von Ineleganzen wie unnötigensleep
Aufrufen beseitigen . Eine schnelle Möglichkeit, dies in Ihrem gesamten Skript zu tun, ist die Verwendung von.set -e
Für Befehle, deren Beendigungsstatus voraussichtlich ungleich Null ist, sind jedoch möglicherweise einige Änderungen erforderlich.quelle
ping
beim Empfang von SIGINT ein Exit-Status von 0 zurückgegeben wird undbash
der SIGINT, den er selbst erhalten hat, dann ignoriert wird, wenn dies der Fall ist. Das Hinzufügen eines "set -e" oder das Überprüfen des Exit-Status hilft hier nicht weiter. Das Hinzufügen einer expliziten Falle für SIGINT würde helfen.Das Terminal bemerkt die Steuerung-c und sendet ein
INT
Signal an die Vordergrundprozessgruppe, die hier die Shell enthält, daping
keine neue Vordergrundprozessgruppe erstellt wurde. Dies kann leicht durch Überfüllen überprüft werdenINT
.Wenn der ausgeführte Befehl eine neue Prozessgruppe im Vordergrund erstellt hat, wechselt das Control-C zu dieser Prozessgruppe und nicht zur Shell. In diesem Fall muss die Shell die Exit-Codes überprüfen, da diese vom Terminal nicht signalisiert werden.
(
INT
Handhabung in Schalen kann fabulously kompliziert sein, nebenbei bemerkt , wie die Shell manchmal das Signal ignorieren muss, und manchmal nicht Quelle taucht wenn neugierig oder ponder:.tail -f /etc/passwd; echo foo
)quelle
ping
hat keinen Grund, hier eine neue Prozessgruppe zu starten, und die Version von ping (iputils unter Debian), mit der ich das OP-Problem reproduzieren kann, erstellt keine Prozessgruppe.Nun, ich habe versucht, ein
sleep 1
zu dem Bash-Skript hinzuzufügen , und los geht's!Jetzt kann ich mit zwei aufhören Ctrl+C.
Beim Drücken von Ctrl+Cwird ein
SIGINT
Signal an den aktuell ausgeführten Prozess gesendet, welcher Befehl innerhalb der Schleife ausgeführt wurde. Anschließend setzt der Subshell- Prozess die Ausführung des nächsten Befehls in der Schleife fort, der einen anderen Prozess startet. Um das Skript anhalten zu können, müssen zweiSIGINT
Signale gesendet werden , eines, um den aktuellen Befehl in der Ausführung zu unterbrechen, und eines, um den Subshell- Prozess zu unterbrechen .Im Skript ohne den
sleep
Aufruf Ctrl+Cscheint es nicht zu funktionieren, sehr schnell und oft zu drücken , und es ist nicht möglich, die Schleife zu verlassen. Ich vermute, dass zweimaliges Drücken nicht schnell genug ist, um es genau im richtigen Moment zwischen der Unterbrechung des aktuell ausgeführten Prozesses und dem Beginn des nächsten zu machen. Bei jedem Ctrl+CTastendruck wird einSIGINT
an einen Prozess gesendet , der in der Schleife ausgeführt wird, jedoch nicht an die Subshell .In dem Skript mit
sleep 1
wird durch diesen Aufruf die Ausführung für eine Sekunde unterbrochen, und wenn die Ausführung durch die erste Ctrl+C(ersteSIGINT
) unterbrochen wird , benötigt die Subshell mehr Zeit, um den nächsten Befehl auszuführen. Nun wird die zweite Ctrl+C(zweiteSIGINT
) in die Subshell verschoben , und die Skriptausführung wird beendet.quelle
Versuche dies:
Ändern Sie nun die erste Zeile in:
und versuchen Sie es erneut - prüfen Sie, ob der Ping jetzt unterbrochen werden kann.
quelle
Zum Beispiel
pgrep -f firefox
wird die PID der Ausführung überprüftfirefox
und diese PID in einer Datei mit dem Namen gespeichertany_file_name
. Der Befehl 'sed' fügt diekill
PID-Nummer am Anfang der Datei 'any_file_name' hinzu. Die dritte Zeileany_file_name
enthält die ausführbare Datei. Die vierte Zeile beendet die in der Datei vorhandene PIDany_file_name
. Das Schreiben der obigen vier Zeilen in eine Datei und das Ausführen dieser Datei kann das Control- C. Für mich absolut in Ordnung.quelle
Wenn jemand an einer Lösung für diese
bash
Funktion interessiert ist und nicht so sehr an der Philosophie dahinter , ist hier ein Vorschlag:Führen Sie den problematischen Befehl nicht direkt aus, sondern führen Sie ihn von einem Wrapper aus, der a) darauf wartet, dass er beendet wird, b) keine Signale verarbeitet und c) den WCE-Mechanismus selbst nicht implementiert, sondern einfach beim Empfang von a abstirbt
SIGINT
.Eine solche Hülle könnte mit
awk
ihrersystem()
Funktion hergestellt werden.Fügen Sie ein Skript wie OPs ein:
quelle
Sie sind Opfer eines bekannten Bash-Bugs. Bash führt eine Jobkontrolle für Skripte durch, was ein Fehler ist.
Was passiert ist, dass Bash die externen Programme in einer anderen Prozessgruppe ausführt, als sie für das Skript selbst verwendet. Da die TTY-Prozessgruppe auf die Prozessgruppe des aktuellen Vordergrundprozesses festgelegt ist, wird nur dieser Vordergrundprozess beendet und die Schleife im Shell-Skript fortgesetzt.
So überprüfen Sie Folgendes: Rufen Sie eine aktuelle Bourne-Shell ab und kompilieren Sie sie, die pgrp (1) als integriertes Programm implementiert. Fügen Sie dann der Skriptschleife / bin / sleep 100 (oder / usr / bin / sleep, abhängig von Ihrer Plattform) hinzu, und starten Sie dann die Borowski-Shell. Nachdem Sie mit ps (1) die Prozess-IDs für den Befehl sleep und die Bash abgerufen haben, die das Skript
pgrp <pid>
ausführt , rufen Sie "<pid>" auf und ersetzen Sie "<pid>" durch die Prozess-ID des Sleep und der Bash, die das Skript ausführt. Sie sehen unterschiedliche Prozessgruppen-IDs. Rufen Sie nun so etwas wie aufpgrp < /dev/pts/7
(ersetzen Sie den tty-Namen durch das vom Skript verwendete tty), um die aktuelle tty-Prozessgruppe zu erhalten. Die TTY-Prozessgruppe entspricht der Prozessgruppe des Sleep-Befehls.Behebung: Verwenden Sie eine andere Shell.
Die neuesten Bourne Shell-Quellen befinden sich in meinem Schily-Tool-Paket, das Sie hier finden:
http://sourceforge.net/projects/schilytools/files/
quelle
bash
ist das? AFAIKbash
tut dies nur, wenn Sie die Option -m oder -i übergeben.bash -c 'ps -j; ps -j; ps -j'
) reproduzieren ./bin/sh -ce
. Ich musste eine hässliche Problemumgehung hinzufügensmake
, die explizit die Prozessgruppe für einen aktuell ausgeführten Befehl beendet^C
, um einen geschichteten Make-Aufruf abbrechen zu können. Haben Sie überprüft, ob bash die Prozessgruppe von der Prozessgruppen-ID geändert hat, mit der sie initiiert wurde?ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'
gibt in allen 3 ps-Aufrufen dieselbe pgid für ps und bash an. (ARGV0 = sh ist einzsh
Weg, argv [0] zu übergeben).