bestätigter Ausgang mit Falle

9

Ich versuche, das Ctrl+CSignal abzufangen und den Benutzer um eine Bestätigung zu bitten. Das Einfangteil funktioniert einwandfrei. Sobald das Signal abgefangen wird, kehrt es nicht zur normalen Ausführung zurück. Stattdessen wird das Skript beendet. So wird die Ausführung fortgesetzt, wenn der Benutzer Nein drückt.

Hier ist mein Code

hell()
{
echo "Do you want to quit? Press 1 for yes and 0 for no";
read n;
if [ $n == 1 ]; then
exit 1;
fi
}

trap "hell" SIGINT

find /
CHID
quelle

Antworten:

12

Was ist los

Wenn Sie Ctrl+ drücken C, wird das SIGINTSignal an die gesamte Vordergrundprozessgruppe gesendet . Hier wird es sowohl an den findProzess als auch an den aufrufenden Shell-Prozess gesendet . findreagiert durch sofortiges Verlassen und die Shell reagiert durch Aufrufen der Falle.

Wenn der Code in der Trap zurückkehrt (dh nicht aufruft exit), wird die Ausführung mit dem Befehl nach dem Befehl fortgesetzt, der durch das Signal unterbrochen wurde. Hier kommt nach dem findBefehl das Ende des Skripts, sodass das Skript trotzdem sofort beendet wird. Sie können den Unterschied zwischen der Eingabe von 0 und 1 erkennen, indem Sie einen weiteren Befehl hinzufügen:

find /
echo "find returned $?"

Ein Weg, um zu tun, was Sie wollen (aber wahrscheinlich nicht tun sollten)

Du kannst machen was du willst; In diesem Teil meiner Antwort geht es jedoch mehr darum, die Shell-Programmierung zu entdecken als ein tatsächliches Problem zu lösen.

  • Aus gestalterischer Sicht ist ein neu startbares Signal nicht das, was Sie normalerweise von relativ einfachen Programmen erwarten würden, die Shell-Skripte normalerweise sind. Die Erwartung ist, dass Ctrl+ Cdas Skript beendet.
  • Wie Sie weiter unten sehen werden, werden die Funktionen der Shell ohnehin ein wenig erweitert.

Wenn Sie das Töten vermeiden möchten find, müssen Sie es im Hintergrund starten : find / &. Warten Sie dann mit dem waiteingebauten Gerät, bis es normal beendet wird. Ein Signal unterbricht die waitintegrierte Funktion, die Sie in einer Schleife ausführen können, bis Sie ein Signal empfangen, das Sie weitergeben möchten. Verwenden Sie dann kill, um den Job zu beenden.

hell () {
  echo "Do you want to quit? Press 1 for yes and 0 for no"
  read n
  if [ "$n" = 1 ]; then
    # Kill the job if it's running, then exit
    if [ -n "$job_pid" ]; then kill $job_pid; fi
    exit 1
  fi
}

job_pid=
trap "hell" SIGINT

# Start a demo job in the background
for i in 1 2 3 4 5; do date; sleep 1; done &
job_pid=$!
# Call wait in a loop; wait will return 0 if the job exits, and 128+$signum if interrupted by a signal.
while ! wait; do
  echo "resuming wait"
done
job_pid=
echo last exit code: $?

Es gibt Einschränkungen für diesen Ansatz in der Shell:

  • Es gibt eine Race-Bedingung: Wenn Sie kurz nach Beendigung des Jobs, aber vor der Leitung, Ctrl+ drücken , versucht der Signalhandler zu töten , aber der Prozess existiert nicht mehr (selbst als Zombie, weil er bereits geerntet wurde) und der Prozess Die ID wurde möglicherweise von einem anderen Prozess wiederverwendet. Dies kann in der Shell nicht einfach behoben werden (möglicherweise durch Festlegen eines Handlers für ?).Cjob_pid=$jobpidwaitSIGCHLD
  • Wenn Sie den Rückgabestatus des Jobs benötigen, müssen Sie das wait $job_pidFormular verwenden. Aber dann kann man nicht unterscheiden, waitob "der Job durch ein Signal unterbrochen wurde" von "der Job wurde durch ein Signal beendet" (oder von "der Job wurde von selbst mit einem Rückgabestatus ≥128 beendet"), aber das ist eine allgemeine Tatsache in der Shell Programmierung).
  • Dies lässt sich nicht leicht auf mehrere Unterjobs ausweiten. Beachten Sie, dass das Verhalten von Traps und Signalen oft überraschend ist, wenn Sie in den meisten Shell-Implementierungen über die Grundlagen hinausgehen (nur ksh macht dies gut).

Verwenden Sie eine schickere Sprache wie Perl oder Python, um diese Einschränkungen zu überwinden.

Gilles 'SO - hör auf böse zu sein'
quelle