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.