Dieses Skript übernimmt die Benutzereingabe Zeile für Zeile und wird myfunction
in jeder Zeile ausgeführt
#!/bin/bash
SENTENCE=""
while read word
do
myfunction $word"
done
echo $SENTENCE
Um die Eingabe zu stoppen, muss der Benutzer [ENTER]
und dann drücken Ctrl+D
.
Wie kann ich mein Skript neu erstellen, um nur mit Ctrl+D
der Zeile zu enden und diese zu verarbeiten, in Ctrl+D
der gedrückt wurde?
bash
shell-script
scripting
read
user-input
user123456
quelle
quelle
function myfunction { echo "you pressed $1" ; };
und sobald ich die Steuerung drücke, wird die D-Schleife beendetecho $sentence
und das Skript beendet.Antworten:
Dazu müssten Sie Zeichen für Zeichen lesen, nicht Zeile für Zeile.
Warum? Die Shell verwendet sehr wahrscheinlich die Standardfunktion der C-Bibliothek
read()
, um die Daten zu lesen, die der Benutzer eingibt, und diese Funktion gibt die Anzahl der tatsächlich gelesenen Bytes zurück. Wenn es Null zurückgibt, bedeutet dies, dass EOF aufgetreten ist (sieheread(2)
Handbuch;man 2 read
). Beachten Sie, dass EOF kein Zeichen, sondern eine Bedingung ist, dh die Bedingung "Es ist nichts mehr zu lesen", Dateiende .Ctrl+Dsendet ein Übertragungsende-Zeichen (EOT, ASCII-Zeichencode 4,
$'\04'
inbash
) an den Terminaltreiber. Dies hat den Effekt, dass alles gesendet wird, was an den wartendenread()
Anruf der Shell gesendet werden soll.Wenn Sie nach der Ctrl+DHälfte der Eingabe des Textes in einer Zeile drücken , wird alles, was Sie bisher eingegeben haben, an die Shell 1 gesendet . Dies bedeutet, dass, wenn Sie Ctrl+Dzweimal eingeben, nachdem Sie etwas in eine Zeile eingegeben haben, der erste einige Daten sendet und der zweite nichts sendet und der
read()
Aufruf Null zurückgibt und die Shell dies als EOF interpretiert. Wenn Sie Entergefolgt von drücken Ctrl+D, erhält die Shell sofort EOF, da keine Daten zum Senden vorhanden waren.Wie kann man also vermeiden, Ctrl+Dzweimal tippen zu müssen ?
Wie gesagt, lesen Sie einzelne Zeichen. Wenn Sie den in die
read
Shell integrierten Befehl verwenden, verfügt dieser wahrscheinlich über einen Eingabepuffer und fordert Sieread()
auf, maximal so viele Zeichen aus dem Eingabestream zu lesen (möglicherweise 16 KB oder so). Dies bedeutet, dass die Shell eine Reihe von 16-KB-Eingabestücken erhält, gefolgt von einem Block, der weniger als 16 KB groß sein kann, gefolgt von Null-Bytes (EOF). Sobald das Ende der Eingabe (oder eine neue Zeile oder ein angegebenes Trennzeichen) erreicht ist, wird die Steuerung an das Skript zurückgegeben.Wenn Sie
read -n 1
ein einzelnes Zeichen lesen, verwendet die Shell beim Aufruf von einen Puffer mit einem einzelnen Byteread()
, dh sie befindet sich in einer engen Schleife und liest Zeichen für Zeichen und gibt nach jedem Zeichen die Kontrolle an das Shell-Skript zurück.Das einzige Problem dabei
read -n
ist, dass das Terminal auf "Raw-Modus" gesetzt wird, was bedeutet, dass Zeichen so gesendet werden, wie sie ohne Interpretation sind. Wenn Sie beispielsweise drücken Ctrl+D, wird in Ihrer Zeichenfolge ein wörtliches EOT-Zeichen angezeigt. Also müssen wir das überprüfen. Dies hat auch den Nebeneffekt, dass der Benutzer die Zeile nicht bearbeiten kann, bevor er sie an das Skript sendet, z. B. durch Drücken Backspaceoder Verwenden von Ctrl+W(zum Löschen des vorherigen Wortes) oder Ctrl+U(zum Löschen am Zeilenanfang). .Um es kurz zu machen: Das Folgende ist die letzte Schleife, die Ihr
bash
Skript ausführen muss, um eine Eingabezeile zu lesen, während der Benutzer die Eingabe jederzeit durch Drücken von unterbrechen kann Ctrl+D:Ohne zu sehr ins Detail zu gehen:
IFS=
löscht dieIFS
Variable. Ohne dies könnten wir keine Leerzeichen lesen. Ich benutzeread -N
stattdessenread -n
, sonst könnten wir keine Zeilenumbrüche erkennen. Die-r
Optionread
ermöglicht es uns, Backslashes richtig zu lesen.Die
case
Anweisung wirkt auf jedes gelesene Zeichen ($ch
). Wenn ein EOT ($'\04'
) erkannt wird, wird esgot_eot
auf 1 gesetzt und fällt dann zu derbreak
Anweisung durch, die es aus der inneren Schleife herausholt. Wenn ein Zeilenumbruch ($'\n'
) erkannt wird, bricht er einfach aus der inneren Schleife aus. Andernfalls wird das Zeichen am Ende derline
Variablen hinzugefügt.Nach der Schleife wird die Zeile auf die Standardausgabe gedruckt. Hier rufen Sie Ihr Skript oder Ihre Funktion auf
"$line"
. Wenn wir durch Erkennen eines EOT hierher gekommen sind, verlassen wir die äußerste Schleife.1 Sie können dies testen, indem Sie
cat >file
in einem Terminal undtail -f file
in einem anderen laufen und dann eine Teilzeile in das eingebencat
und drücken, um Ctrl+Dzu sehen, was in der Ausgabe von passierttail
.Für
ksh93
Benutzer: In der obigen Schleife wird ein Wagenrücklaufzeichen anstelle eines Zeilenumbruchzeichens gelesen. Diesksh93
bedeutet, dass der Test für$'\n'
in einen Test für geändert werden muss$'\r'
. Die Shell zeigt diese auch als an^M
.Um dies zu umgehen:
Möglicherweise möchten Sie auch eine neue Zeile explizit kurz vor dem ausgeben
break
, um genau das gleiche Verhalten wie in zu erhaltenbash
.quelle
read -N
den Backslash / Strg-W / Strg-U nicht verwenden kann, um den Text zu bearbeiten, oder Strg-V, um Spezial einzugeben , da das tty-Gerät den kanonischen Modus verlässt Figuren.Im Standardmodus des Endgeräts würde der
read()
Systemaufruf (wenn er mit einem ausreichend großen Puffer aufgerufen wird) zu vollen Leitungen führen. Das einzige Mal, wenn die gelesenen Daten nicht mit einem Zeilenumbruch enden, ist das Drücken von Ctrl-D.In meinen Tests (unter Linux, FreeBSD und Solaris)
read()
ergibt eine einzelne immer nur eine einzelne Zeile, selbst wenn der Benutzer zum Zeitpunkt des Aufrufs mehr eingegeben hatread()
. Der einzige Fall , in dem die gelesenen Daten mehr als eine Zeile enthalten könnten , wäre, wenn der Benutzer eine neue Zeile als eintritt Ctrl+VCtrl+J(die wörtlichen-nächsten Zeichen durch ein wörtliches Newline - Zeichen gefolgt (im Gegensatz zu einem Wagenrücklauf im Gegensatz zu Newline konvertierten , wenn Sie drücken Enter)) .Die
read
eingebaute Shell liest die Eingabe jedoch byteweise, bis ein Zeilenumbruchzeichen oder ein Dateiende angezeigt wird. Dieses Dateiende ist , wennread(0, buf, 1)
0 zurückgegeben wird, was nur passieren kann, wenn Sie Ctrl-Dauf eine leere Zeile drücken .Hier möchten Sie große Lesevorgänge durchführen und feststellen, Ctrl-Dwann die Eingabe nicht mit einem Zeilenumbruch endet.
Sie können das nicht mit dem
read
eingebauten tun, aber Sie könnten es mit demsysread
eingebauten von tunzsh
.Wenn Sie den Benutzer berücksichtigen möchten, der Folgendes eingibt
^V^J
:Wenn Sie
foo^V^Jbar
einen einzelnen Datensatz (mit einem eingebetteten Zeilenumbruch) betrachten möchten, wird davon ausgegangen, dass jederread()
einen Datensatz zurückgibt:Alternativ können
zsh
Sie mitzsh
den eigenen erweiterten Zeileneditor verwenden, um die Daten einzugeben und^D
dort einem Widget zuzuordnen , das das Ende der Eingabe signalisiert:Mit
bash
oder anderen POSIX-Shells können Sie für ein Äquivalent dessysread
Ansatzes etwas Annäherendes tun, indemdd
Sie dieread()
Systemaufrufe ausführen:quelle
zsh
, dass ich unter "Feature Bloat" leide, aber es stellt sich heraus, dass einige der Features manchmal recht praktisch zu sein scheinen. Über dasdd
imsh
Skript:8192
Können Sie kommentieren, warum Sie Bytes (8 KB) lesen ?sysread
standardmäßig von zsh verwendet (auch das dort unterstützte Maximum). Welche Funktion vonzsh
finden Sie nicht nützlich? Beachten Sie, dass sichzsh
Code auch in verschiedenen dynamischen Modulen befindet, sodass das Aufblähen ihn nicht so stark beeinflusst wie andere Shells wie ksh93 oder bash.Ich bin mir nicht ganz sicher, wonach Sie fragen, aber wenn Sie möchten, dass der Benutzer mehrere Zeilen eingeben und dann alle Zeilen als Ganzes verarbeiten kann, können Sie sie verwenden
mapfile
. Es nimmt Benutzereingaben auf, bis EOF angetroffen wird, und gibt dann ein Array zurück, wobei jede Zeile ein Element im Array ist.quelle