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 (siehe read(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'
in bash
) an den Terminaltreiber. Dies hat den Effekt, dass alles gesendet wird, was an den wartenden read()
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 Sie read()
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 Byte read()
, 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:
while true; do
line=''
while IFS= read -r -N 1 ch; do
case "$ch" in
$'\04') got_eot=1 ;&
$'\n') break ;;
*) line="$line$ch" ;;
esac
done
printf 'line: "%s"\n' "$line"
if (( got_eot )); then
break
fi
done
Ohne zu sehr ins Detail zu gehen:
IFS=
löscht die IFS
Variable. Ohne dies könnten wir keine Leerzeichen lesen. Ich benutze read -N
stattdessen read -n
, sonst könnten wir keine Zeilenumbrüche erkennen. Die -r
Option read
ermöglicht es uns, Backslashes richtig zu lesen.
Die case
Anweisung wirkt auf jedes gelesene Zeichen ( $ch
). Wenn ein EOT ( $'\04'
) erkannt wird, wird es got_eot
auf 1 gesetzt und fällt dann zu der break
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 der line
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 und tail -f file
in einem anderen laufen und dann eine Teilzeile in das eingeben
cat
und drücken, um Ctrl+Dzu sehen, was in der Ausgabe von passiert tail
.
Für ksh93
Benutzer: In der obigen Schleife wird ein Wagenrücklaufzeichen anstelle eines Zeilenumbruchzeichens gelesen. Dies ksh93
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:
stty_saved = "$ (stty -g)"
stty -echoctl
# Die Schleife geht hierher, wobei $ '\ n' durch $ '\ r' ersetzt wird.
stty "$ stty_saved"
Möglicherweise möchten Sie auch eine neue Zeile explizit kurz vor dem ausgeben break
, um genau das gleiche Verhalten wie in zu erhalten bash
.
function myfunction { echo "you pressed $1" ; };
und sobald ich die Steuerung drücke, wird die D-Schleife beendetecho $sentence
und das Skript beendet.