Gibt es einen `read -n`-äquivalenten Befehl für die Bourne-Shell?

Gibt es einen `read -n`-äquivalenten Befehl für die Bourne-Shell?

Gibt es eine Möglichkeit, mein Skript warten zu lassen, bis der Benutzer eine beliebige Taste drückt, und dann fortzufahren, ohne dass er drücken muss Enter? Ich möchte, dass es in der Bourne-Shell () funktioniert sh, nicht in Bash.

Antwort1

Vorbemerkungen

Heutzutage shistnicht unbedingt die alte Bourne-Shell. Heutzutage shist eine Shell, die unterstütztmindestensdie von POSIX geforderten Funktionen (bei korrekter Implementierung). POSIX spezifiziert dieshDienstprogrammund dasShell-Befehlssprache.

Ich gehe davon aus, dass Sie möchten, dass Ihr Code in einer POSIX-Shell funktioniert und generell portierbar ist.


Probleme

dd ibs=1 count=…ist eine POSIX-Methode zum Lesen einer genauen Anzahl von Bytes. Es scheint das einzige portable Kommandozeilenprogramm zu sein, das diese Aufgabe zuverlässig erledigen kann. Aber ddes liest Bytes, während read -nBashFiguren.POSIX erlaubt Multibyte-Zeichen in anderen Gebietsschemas alsPOSIX. Selbst wenn Sie das gesamte Skript mit ausführen LC_ALL=POSIX, kann das Terminal (Terminalemulator) bei einem einzelnen Tastendruck dennoch eine Mehrbyte-Sequenz generieren. Eine solche Sequenz muss kein Mehrbyte-Zeichen sein; es kann eine Escape-Sequenz sein (zB fürF1, siehe auchdiese Antwort).

Nachdem Sie nur ein Byte gelesen haben, bleibt der Rest erhalten und wird von allem gelesen, was später versucht, vom Terminal zu lesen (dies kann ein Teil Ihres Skripts oder der interaktiven Shell sein, von der aus Sie das Skript ausführen).

Wenn Sie zusätzlich Sohle laufen dd ibs=1 count=1, dann werden Sie höchstwahrscheinlich feststellen, dass nichts kommt, ddbis Sie überschreiten{MAX_INPUT}oder{MAX_CANON}, oder drücken Sie EnteroderCtrl+D(und falls countes mehr als sind 1, wenn Sie weniger Bytes angeben möchten, müssen Sie möglicherweise Ctrl+ drückenD zweimal).

Um diese Probleme zu lösen, benötigen Sie dienichtkanonischer Modus. Verwendenstty -icanon. Generell stty rawscheint es eine gute Idee zu sein, mit zu beginnen.


Code

Das folgende Beispiel ist ein Proof of Concept. Da es die Zeileneinstellungen des Terminals manipuliert und vom Terminal liestDudarf nichtfüge es in eine interaktive Shell ein, das wird nicht funktionieren. Fügen Sie es in eine Datei ein und führen Sie die Datei aus.

#!/bin/sh

echo "Press any key to continue."

saveterm="$(stty -g)"                     # save terminal state
stty raw
stty -echo -icanon min 1 time 0           # prepare to read one byte
dd ibs=1 count=1 >/dev/null 2>/dev/null   # read one byte
stty -icanon min 0 time 0                 # prepare to read lefotvers
while read none; do :; done               # read leftovers
stty "$saveterm"                          # restore terminal state

Anmerkungen

  • Das Skript überprüft nicht, ob sein Standardeingabefeld ein Terminal ist, aber im Allgemeinen sollte es ([ -t 0 ]).

  • In einer vernünftigen Konfiguration sendet eine Modifikatortaste (z. B. Shift), wenn sie allein gedrückt wird, keine Eingabe an das, was vom Terminal liest; deshalb wird unser Code eine solche Taste nicht als „beliebige Taste“ registrieren.

  • Der Trick mit den „Resten lesen“ stammt ausdiese Antwort.

  • Dank stty raw Ctrl+ Coder Ctrl+ Zkann auch „jede beliebige Taste“ sein, anstatt SIGINTbzw. zu senden SIGSTOP. Die Shell, die das Skript interpretiert, kann dennoch ein Signal von woanders erhalten, sodass es im Allgemeinen passieren kann, dass es beendet wird, bevor es zu kommt stty "$saveterm"; es kann also passieren, dass es das Terminal in einem Zustand hinterlässt, der für die interaktive Nutzung nicht geeignet ist. Möglicherweise möchten Sie relevante Signale abfangen und den Anfangszustand des Terminals trotzdem wiederherstellen.

  • Das ist wahrVariablen sollten in doppelte Anführungszeichen gesetzt werdenim Allgemeinen. Hier $savetermwird absichtlich nicht in Anführungszeichen gesetzt, da stty -geine nicht angegebene Ausgabe erzeugt wird. Implementierungen von sttydürfen eine Ausgabe mit Leerzeichen erzeugen und später erwarten, dass die Shell sie aufteilt und mehrere Argumente übergibt. Angegeben ist eine Einschränkung, dass die Ausgabe von stty -gohne Anführungszeichen sicher sein muss und keine Worterweiterung in einer Shell auslösen darf. Aus Gründen der Portabilität $savetermist eine Ausgabe ohne Anführungszeichen besser.

    Bearbeiten:das war ein Defekt in der POSIX-Spezifikation. Der obige Code wurde behoben.

  • Um zu wissen, welches Byte gelesen wurde, müssen Sie die Ausgabe von speichern dd, entweder in einer normalen Datei ( >some_file), die Sie später untersuchen werden, oder in einer Variablen ( variable="$(dd …)"). Aber:

    • Ein Terminal kann ein Nullbyte generieren (üblicherweise mit Ctrl+ @), aber die meisten (alle?) Implementierungen von shkönnen ein Nullbyte nicht in einer Variablen speichern. Das Speichern in einer Datei ist OK, wenn Sie nur deren Inhalt untersuchen/manipulieren können, ohne ihn in eine Variable einzulesen.
    • ddaus dem obigen Code erhalten Sie nur ein Byte, das reicht möglicherweise nicht aus, um das Zeichen oder den Schlüssel anzugeben. Andererseits sollte ein einzelnes Byte ausreichen, um z. B. Qvon allem anderen zu unterscheiden, sodass eine Implementierung ohne Analyse weiterer Bytes möglich Press Q to quit or any other key to continue.scheint P - proceed; B - back; Q - quit; H - help.

Wie wäre es mit mehr Bytes?

Das obige sollte für "Drücken Sie eine beliebige Taste" in Ordnung sein. Ein echtes Äquivalent von read -nsollte jeweils ein Byte lesen und die Sequenz dekodieren, bis die gewünschte Anzahl vonFiguren. Ich werde nicht versuchen, es hier zu bauen.

verwandte Informationen