GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Kann ein Bash-Skript nicht mit Strg + C stoppen?

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).

Verwandte:#!/bin/bash – keine solche Datei oder Verzeichnis?

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 haben
trap '
  trap - INT # restore default INT handler
  kill -s INT "$$"
' INT

Linux
  1. Berechtigung zum Ausführen von cron mit bash.sh verweigert

  2. Bash-Skript im Leerlauf, bis das STRG+C-Ereignis protokolliert wird

  3. Hinzufügen zu $PYTHONPATH mit Bash-Skript

  4. Wie man Python-Skript mit Bash-Skript beendet

  5. Führen Sie den Bash-Befehl auf der Jenkins-Pipeline aus

So schreiben Sie ein Bash-Skript mit Beispielen

Shell-Scripting Teil I:Erste Schritte mit Bash-Scripting

35 Bash-Skriptbeispiele

Bash-Anfängerserie Nr. 10:Automatisierung mit Bash

Bash Scripting Einführungstutorial mit 5 praktischen Beispielen

Bash-Skript für Schleife mit Beispielen erklärt