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 sh
istnicht unbedingt die alte Bourne-Shell. Heutzutage sh
ist eine Shell, die unterstütztmindestensdie von POSIX geforderten Funktionen (bei korrekter Implementierung). POSIX spezifiziert diesh
Dienstprogrammund 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 dd
es liest Bytes, während read -n
BashFiguren.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, dd
bis Sie überschreiten{MAX_INPUT}
oder{MAX_CANON}
, oder drücken Sie EnteroderCtrl+D(und falls count
es 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 raw
scheint 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, anstattSIGINT
bzw. zu sendenSIGSTOP
. 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 kommtstty "$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$saveterm
wird absichtlich nicht in Anführungszeichen gesetzt, dastty -g
eine nicht angegebene Ausgabe erzeugt wird. Implementierungen vonstty
dü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 vonstty -g
ohne Anführungszeichen sicher sein muss und keine Worterweiterung in einer Shell auslösen darf. Aus Gründen der Portabilität$saveterm
ist 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
sh
kö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. dd
aus 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öglichPress Q to quit or any other key to continue.
scheintP - proceed; B - back; Q - quit; H - help
.
- Ein Terminal kann ein Nullbyte generieren (üblicherweise mit Ctrl+ @), aber die meisten (alle?) Implementierungen von
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 -n
sollte jeweils ein Byte lesen und die Sequenz dekodieren, bis die gewünschte Anzahl vonFiguren. Ich werde nicht versuchen, es hier zu bauen.