Ich verstehe natürlich, dass man der internen Feldtrennervariablen einen Wert hinzufügen kann. Zum Beispiel:
$ IFS=blah
$ echo "$IFS"
blah
$
Ich verstehe auch, dass read -r line
speichert Daten von stdin
in die Variable namens line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
Wie kann ein Befehl jedoch einen Variablenwert zuweisen? Und speichert es zuerst Daten von stdin
zur Variablen line
und geben Sie dann den Wert von line
an zu IFS
?
Akzeptierte Antwort:
In POSIX-Shells read
, ohne Option liest keine Zeile , es liest Wörter aus einer (möglicherweise mit Backslash fortgesetzten) Zeile, in der Wörter $IFS
sind delimited und Backslash können verwendet werden, um die Trennzeichen (oder Fortsetzungszeilen) zu maskieren.
Die allgemeine Syntax lautet:
read word1 word2... remaining_words
read
liest stdin Byte für Byte¹, bis es ein Zeilenumbruchzeichen ohne Escapezeichen (oder das Ende der Eingabe) findet, teilt es nach komplexen Regeln auf und speichert das Ergebnis dieser Aufteilung in $word1
, $word2
… $remaining_words
.
Zum Beispiel bei einer Eingabe wie:
<tab> foo bar baz blah blah
whatever whatever
und mit dem Standardwert $IFS
, read a b c
würde zuweisen:
$a
⇐foo
$b
⇐bar baz
$c
⇐blah blahwhatever whatever
Wenn jetzt nur ein Argument übergeben wird, wird das nicht zu read line
. Es ist immer noch read remaining_words
. Die Backslash-Verarbeitung erfolgt weiterhin, IFS-Leerzeichen² werden weiterhin am Anfang und am Ende entfernt.
Das -r
Option entfernt die Backslash-Verarbeitung. Also derselbe Befehl oben mit -r
würde stattdessen zuweisen
$a
⇐foo
$b
⇐bar
$c
⇐baz blah blah
Nun, für den Aufteilungsteil ist es wichtig zu erkennen, dass es zwei Klassen von Zeichen für $IFS
gibt :die IFS-Leerzeichen² (einschließlich Leerzeichen und Tabulator (und Zeilenumbruch, obwohl das hier keine Rolle spielt, es sei denn, Sie verwenden -d), die zufällig auch im Standardwert von $IFS
enthalten sind ) und die Anderen. Die Behandlung dieser beiden Zeichenklassen ist unterschiedlich.
Mit IFS=:
(:
kein IFS-Leerzeichen), eine Eingabe wie :foo::bar::
würde in ""
aufgeteilt werden , "foo"
, ""
, bar
und ""
(und ein zusätzliches ""
bei einigen Implementierungen spielt das aber keine Rolle, außer bei read -a
). Während wir diesen :
ersetzen mit Leerzeichen erfolgt die Aufteilung nur in foo
und bar
. Das heißt, führende und nachfolgende werden ignoriert, und Sequenzen von ihnen werden wie eine behandelt. Es gibt zusätzliche Regeln, wenn Whitespace- und Nicht-Whitespace-Zeichen in $IFS
kombiniert werden . Einige Implementierungen können die Sonderbehandlung hinzufügen/entfernen, indem sie die Zeichen in IFS verdoppeln (IFS=::
oder IFS=' '
).
Wenn wir also hier nicht wollen, dass die führenden und nachgestellten Leerzeichen ohne Escapezeichen entfernt werden, müssen wir diese IFS-Leerzeichen aus IFS entfernen.
Selbst bei IFS-Nicht-Leerzeichen, wenn die Eingabezeile eines (und nur eines) dieser Zeichen enthält und es das letzte Zeichen in der Zeile ist (wie IFS=: read -r word
bei einer Eingabe wie foo:
) mit POSIX-Shells (nicht zsh
noch irgendein pdksh
Versionen), wird diese Eingabe als ein foo
betrachtet Wort, weil in diesen Shells die Zeichen $IFS
gelten als Terminatoren , also word
wird foo
enthalten , nicht foo:
.
Also die kanonische Art, eine Eingabezeile mit read
zu lesen eingebaut ist:
IFS= read -r line
(Beachten Sie, dass für die meisten read
Implementierungen, die nur für Textzeilen funktionieren, da das NUL-Zeichen außer in zsh
nicht unterstützt wird ).
Verwenden von var=value cmd
Syntax stellt sicher, dass IFS
wird nur für die Dauer dieses cmd
anders gesetzt Befehl.
Anmerkung zur Geschichte
Der read
builtin wurde von der Bourne-Shell eingeführt und sollte bereits Wörter lesen , keine Linien. Es gibt ein paar wichtige Unterschiede zu modernen POSIX-Shells.
read
der Bourne-Shell unterstützte kein -r
-Option (die von der Korn-Shell eingeführt wurde), sodass es keine andere Möglichkeit gibt, die Backslash-Verarbeitung zu deaktivieren, als die Eingabe mit so etwas wie sed 's/\/&&/g'
vorzuverarbeiten dort.
Die Bourne-Shell hatte diese Vorstellung von zwei Klassen von Zeichen (die wiederum von ksh eingeführt wurden) nicht. In der Bourne-Shell werden alle Zeichen genauso behandelt wie IFS-Leerzeichen in ksh, also IFS=: read a b c
bei einer Eingabe wie foo::bar
würde bar
zuweisen zu $b
, nicht die leere Zeichenfolge.
In der Bourne-Shell mit:
var=value cmd
Wenn cmd
ist eine eingebaute (wie read
ist), var
bleibt auf value
gesetzt nach cmd
hat beendet. Das ist besonders kritisch bei $IFS
weil in der Bourne-Shell $IFS
wird verwendet, um alles aufzuteilen, nicht nur die Erweiterungen. Auch, wenn Sie das Leerzeichen aus $IFS
entfernen in der Bourne-Shell "[email protected]"
funktioniert nicht mehr.
In der Bourne-Shell führt das Umleiten eines zusammengesetzten Befehls dazu, dass er in einer Subshell ausgeführt wird (in den frühesten Versionen wurden sogar Dinge wie read var < file
oder exec 3< file; read var <&3
lesen hat nicht funktioniert), daher wurde in der Bourne-Shell selten read
verwendet für alles außer Benutzereingaben auf dem Terminal (wo diese Zeilenfortsetzungsbehandlung sinnvoll war)
Einige Unices (wie HP/UX, es gibt auch einen in util-linux
) haben noch eine line
Befehl zum Lesen einer Eingabezeile (bis zur Version 2 der Single UNIX Specification war dies ein Standard-UNIX-Befehl).
Das ist im Grunde dasselbe wie head -n 1
außer dass es jeweils ein Byte liest, um sicherzustellen, dass es nicht mehr als eine Zeile liest. Auf diesen Systemen können Sie Folgendes tun:
line=`line`
Das bedeutet natürlich, einen neuen Prozess zu erzeugen, einen Befehl auszuführen und seine Ausgabe über eine Pipe zu lesen, also viel weniger effizient als die IFS= read -r line
von ksh , aber immer noch viel intuitiver.