Drücken Sie eine beliebige Taste, um eine Schleife zu stoppen

Drücken Sie eine beliebige Taste, um eine Schleife zu stoppen

Wie geht man folgendermaßen vor?

  • Ich habe ein Loop-Skript.
  • Ich möchte es stoppen, wenn ich eine Taste drücke, sonst wird es in 5 Sekunden fortgesetzt.

Antwort1

Bedenken Sie, dass Shells oder andere Anwendungen, die Sie normalerweise in einem Terminal ausführen, nicht mit Tastatur und Bildschirm interagieren.

Sie nehmen die Eingabe von ihrem Standardeingang als Bytestrom entgegen. Wenn dieser Standardeingang aus einer normalen Datei stammt, kommen die Bytes von dort, wenn es sich um eine Pipe handelt, sind das Daten, die normalerweise von einem anderen Prozess gesendet werden, wenn es sich um eine Gerätedatei handelt, die an physische Geräte gesendet werden kann, die an den Computer angeschlossen sind. Wenn es sich beispielsweise um ein TTY-Zeichengerät handelt, sind das Daten, die normalerweise über eine serielle Leitung von einem Terminal gesendet werden. Ein Terminal ist eine Art Gerät, das Tastatureingaben in Bytefolgen umwandelt.

Hier liegt die ganze Leistungsfähigkeit von Terminalanwendungen. Für sie wurde der Eingabemechanismus abstrahiert, sodass sie interaktiv oder automatisch in Skripten verwendet werden können.

Wenn Sie diese Art von Eingabeaufforderung ausgeben und eineSchlüsselPress-Ereignis, Sie möchten wahrscheinlich, dass Ihre Anwendung (Ihr Skript) nur interaktiv ist. Erwarten Sie entweder, dass stdin ein Terminal ist, oder nehmen Sie Eingaben vom Terminal entgegen, unabhängig davon, auf welchem ​​stdin geöffnet ist.

Wie oben zu sehen ist, sind alle Anwendungen, die sie sehen, Byteströme. Das ist die Aufgabe des Terminals (oder Terminalemulators) und der TTY-Gerätezeilendisziplin, gedrückte Tasten in Bytefolgen zu übersetzen. Einige Beispiele:

  • Wenn Sie die Taste drücken a, senden ASCII-Terminals ein 0x61-Byte,
  • Beim Drücken der £Taste senden UTF-8-Terminals die beiden Bytes 0xc2 und 0xa3.
  • Wenn Sie die EnterTaste drücken, senden ASCII-Terminals ein 0x0d-Byte, das TTY-Zeilendisziplinen auf ASCII-basierten Systemen wie Linux normalerweise in 0x0a übersetzen.
  • Wenn Sie Ctrlalleine drücken, senden Terminals nichts, aber wenn Sie gleichzeitig drücken C, senden Terminals ein 0x03-Byte, das die TTY-Leitungsdisziplin abfängt, um ein SIGINT-Signal an die Vordergrundaufgabe zu senden.
  • Wenn Sie drücken Left, senden Terminals normalerweise eine Bytefolge (variiert je nach Terminal, Anwendungen können die Terminfo-Datenbank zur Übersetzung abfragen), deren erstes Byte 0x1b ist. Beispielsweise xtermsendet , je nach Modus, auf ASCII-basierten Systemen entweder 0x1b 0x4f 0x44 oder 0x1b 0x5b 0x44 ( <ESC>[Aoder <ESC>OA).

Hier wären also die Fragen, die ich stellen würde:

  1. Möchten Sie den Benutzer immer noch fragen, ob stdin kein Terminal ist
  2. Wenn die Antwort auf 1 „Ja“ lautet, möchten Sie den Benutzer dann am Terminal oder über stdin/stdout auffordern?
  3. Wenn die Antwort auf 1 Nein ist, möchten Sie trotzdem 5 Sekunden zwischen jeder Iteration warten?
  4. Wenn die Antwort auf 2 lautetüber das Terminal, soll das Skript abgebrochen werden, wenn es kein Steuerterminal erkennen kann, oder in den Nicht-Terminalmodus zurückkehren?
  5. Möchten Sie nur die Tasten berücksichtigen, die nach der Eingabeaufforderung gedrückt wurden? Mit anderen Worten, wenn der Benutzer versehentlich eine Taste drückt, bevor die Eingabeaufforderung ausgegeben wird.
  6. Wie weit sind Sie bereit zu gehen, um sicherzustellen, dass Sie nur die Bytes lesen, die bei einem einzelnen Tastendruck ausgegeben werden?

Hier gehe ich davon aus, dass Ihr Skript nur eine interaktive Terminalanwendung sein soll und nur über das Steuerterminal interagieren soll, wobei stdin/stdout unverändert bleiben sollen.

#! /bin/sh -

# ":" being a special builtin, POSIX requires it to exit if a
# redirection fails, which makes this a way to easily check if a
# controlling terminal is present and readable:
:</dev/tty

# if using bash however not in POSIX conformance mode, you'll need to
# change it to something like:
exec 3< /dev/tty 3<&- || exit

read_key_with_timeout() (
  timeout=$1 prompt=$2
  saved_tty_settings=$(stty -g) || exit

  # if we're killed, restore the tty settings, the convoluted part about
  # killing the subshell process is to work around a problem in shells
  # like bash that ignore a SIGINT if the current command being run handles
  # it.
  for sig in INT TERM QUIT; do
    trap '
      stty "$saved_tty_settings"
      trap - '"$sig"'
      pid=$(exec sh -c '\''echo "$PPID"'\'')
      kill -s '"$sig"' "$pid"

      # fall back if kill failed above
      exit 2' "$sig"
  done

  # drain the tty's buffer
  stty -icanon min 0 time 0; cat > /dev/null

  printf '%s\n' "$prompt"

  # use the tty line discipline features to say the next read()
  # should wait at most the given number of deciseconds (limited to 255)
  stty time "$((timeout * 10))" -echo

  # do one read and count the bytes returned
  count=$(dd 2> /dev/null count=1 | wc -c)

  # If the user pressed a key like the £ or Home ones described above
  # it's likely all the corresponding bytes will have been read by dd
  # above, but not guaranteed, so we may want to drain the tty buffer
  # again to make sure we don't leave part of the sequence sent by a
  # key press to be read by the next thing that reads from the tty device
  # thereafter. Here allowing the terminal to send bytes as slow as 10
  # per second. Doing so however, we may end up reading the bytes sent
  # upon subsequent key presses though.
  stty time 1; cat > /dev/null

  stty "$saved_tty_settings"

  # return whether at least one byte was read:
  [ "$(($count))" -gt 0 ]

) <> /dev/tty >&0 2>&0

until
  echo "Hello World"
  sleep 1
  echo "Done greeting the world"
  read_key_with_timeout 5 "Press any key to stop"
do
  continue
done

Antwort2

while true; do
    echo 'Looping, press Ctrl+C to exit'
    sleep 5
done

Es besteht keine Notwendigkeit, es komplizierter zu machen.

Dafür ist folgendes erforderlich bash:

while true; do
    echo 'Press any key to exit, or wait 5 seconds'
    if read -r -N 1 -t 5; then
        break
    fi
done

Wenn dies readfehlschlägt (durch Zeitüberschreitung), wird die Schleife fortgesetzt.

verwandte Informationen