Ich verstehe natürlich, dass man der internen Feldtrennungsvariablen einen Wert hinzufügen kann. Zum Beispiel:
$ IFS=blah
$ echo "$IFS"
blah
$
Ich verstehe auch, dass read -r line
Daten von stdin
in Variable mit dem Namen speichern line
:
$ read -r line <<< blah
$ echo "$line"
blah
$
Wie kann ein Befehl jedoch einen variablen Wert zuweisen? Und speichert es zuerst Daten von stdin
bis Variable line
und gibt dann Wert von line
bis IFS
?
bash
shell-script
Martin
quelle
quelle
Antworten:
Einige Leute haben die falsche Vorstellung, dass
read
es der Befehl ist, eine Zeile zu lesen. Es ist nicht.read
Liest Wörter aus einer (möglicherweise mit Backslash fortgesetzten) Zeile, in der Wörter durch$IFS
Trennzeichen getrennt sind und Backslash verwendet werden kann, um die Trennzeichen zu umgehen (oder Zeilen fortzusetzen).Die generische Syntax lautet:
read
stdin liest ein Byte zu einer Zeit , bis er eine unescaped Zeilenende- Zeichen (oder End-of-Eingang) findet, aufteilt , dass das Ergebnis dieser Aufteilung in zu komplexen Regeln und speichert nach$word1
,$word2
...$remaining_words
.Zum Beispiel bei einer Eingabe wie:
und mit dem Standardwert
$IFS
,read a b c
würde zuweisen:$a
⇐foo
$b
⇐bar baz
$c
⇐blah blahwhatever whatever
Nun, wenn nur ein Argument übergeben wird, wird das nicht
read line
. Es ist immer noch soread remaining_words
. Die Verarbeitung von umgekehrten Schrägstrichen wird weiterhin durchgeführt. IFS-Leerzeichen werden weiterhin am Anfang und am Ende entfernt.Die
-r
Option entfernt die Backslash-Verarbeitung. Also würde derselbe Befehl wie oben mit-r
vergeben$a
⇐foo
$b
⇐bar\
$c
⇐baz bl\ah blah\
Für den aufteilenden Teil ist es wichtig zu
$IFS
wissen, dass es zwei Klassen von Zeichen gibt : die IFS-Whitespace-Zeichen (nämlich Leerzeichen und Tabulatoren (und Zeilenumbrüche, obwohl dies hier keine Rolle spielt, wenn Sie -d verwenden), die ebenfalls vorkommen im Standardwert von$IFS
) und den anderen sein. Die Behandlung dieser beiden Charakterklassen ist unterschiedlich.Mit
IFS=:
(:
wobei keine IFS Leerzeichen), wie ein Eingang:foo::bar::
in aufgeteilt werden würde""
,"foo"
,""
,bar
und""
(und eine extra""
mit einigen Implementierungen obwohl das nicht mit Ausnahme keine Rolleread -a
). Wenn wir dies durch:
Leerzeichen ersetzen, erfolgt die Aufteilung nur infoo
undbar
. Das heißt, führende und nachfolgende werden ignoriert, und Sequenzen von ihnen werden wie eine behandelt. Es gibt zusätzliche Regeln, wenn Leerzeichen und Nicht-Leerzeichen kombiniert werden$IFS
. Einige Implementierungen können die Sonderbehandlung durch Verdoppeln der Zeichen in IFS (IFS=::
oderIFS=' '
) hinzufügen / entfernen .Wenn wir also nicht möchten, dass die führenden und nachfolgenden Leerzeichen ohne Leerzeichen entfernt werden, müssen wir diese IFS-Leerzeichen aus IFS entfernen.
Selbst bei IFS-Zeichen ohne Leerzeichen wird diese Eingabe durchgeführt, wenn die Eingabezeile eines (und nur eines) dieser Zeichen enthält und es sich um das letzte Zeichen in der Zeile handelt (wie
IFS=: read -r word
bei einer Eingabe wiefoo:
), die POSIX-Shells enthält (nichtzsh
oder in einigenpdksh
Versionen) gilt als eine betrachtetfoo
in diesen Schalen , weil Wort, die Zeichen$IFS
werden als als Terminatoren , soword
enthaltenfoo
, nichtfoo:
.Der kanonische Weg, eine Eingabezeile mit dem
read
eingebauten Code zu lesen, ist:(Beachten Sie, dass dies bei den meisten
read
Implementierungen nur für Textzeilen funktioniert, da das NUL-Zeichen nur in unterstützt wird.zsh
)Durch
var=value cmd
die Verwendung der Syntax wird sichergestellt, dassIFS
nur für die Dauer diesescmd
Befehls ein anderer Wert festgelegt wird.Geschichtsnotiz
Das
read
Builtin wurde von der Bourne-Shell eingeführt und sollte schon Worte , keine Zeilen lesen . Es gibt einige wichtige Unterschiede zu modernen POSIX-Shells.Die Bourne-Shell
read
unterstützt keine-r
Option (die von der Korn-Shell eingeführt wurde), daher gibt es keine Möglichkeit, die Backslash-Verarbeitung zu deaktivieren, außer die Eingabe mit so etwas wie diesersed 's/\\/&&/g'
vorzuverarbeiten.Die Bourne-Shell hatte nicht die Vorstellung von zwei Klassen von Zeichen (die wiederum von ksh eingeführt wurde). In der Bourne - Shell alle Zeichen der gleichen Behandlung unterzogen werden, wie IFS Leerzeichen in KSH tun, ist , dass
IFS=: read a b c
auf einem Eingangs wiefoo::bar
zuweisen würde ,bar
um$b
nicht den leeren String.In der Bourne-Shell mit:
Wenn
cmd
es ein eingebautes ist (wie esread
ist),var
bleibt es auf eingestellt,value
nachdemcmd
es fertig ist. Das ist besonders kritisch,$IFS
da in der Bourne-Shell$IFS
alles aufgeteilt wird, nicht nur die Erweiterungen. Wenn Sie das Leerzeichen$IFS
in der Bourne-Shell entfernen ,"$@"
funktioniert dies ebenfalls nicht mehr.In der Bourne-Shell führt das Umleiten eines zusammengesetzten Befehls dazu, dass dieser in einer Subshell ausgeführt wird (in den frühesten Versionen funktionierten sogar Dinge wie
read var < file
oderexec 3< file; read var <&3
funktionierten nicht). In der Bourne-Shell war es daher selten,read
etwas anderes als Benutzereingaben auf dem Terminal zu verwenden (wo diese Zeilenfortsetzungsbehandlung Sinn machte)Einige Unices (wie HP / UX, es gibt auch einen in
util-linux
) haben noch einenline
Befehl zum Lesen einer Eingabezeile (der bis zur Single UNIX Specification Version 2 ein Standard-UNIX-Befehl war ).Das ist im Grunde dasselbe, mit der
head -n 1
Ausnahme, dass jeweils ein Byte gelesen wird, um sicherzustellen, dass nicht mehr als eine Zeile gelesen wird. Auf diesen Systemen können Sie Folgendes ausführen:Das bedeutet natürlich, einen neuen Prozess zu erzeugen, einen Befehl auszuführen und seine Ausgabe über eine Pipe zu lesen
IFS= read -r line
, was viel weniger effizient ist als die von ksh , aber dennoch viel intuitiver.quelle
sh
Unterschieden ist auch nützlich, um tragbare Skripte zu schreiben!)bash-4.4.19
,while read -r; do echo "'$REPLY'"; done
arbeitet alswhile IFS= read -r line; do echo "'$line'"; done
.read
es etwas anderes geben muss , wenn die Verwendung zum Lesen einer Zeile fehlerhaft ist. Was könnte diese nicht-falsche Vorstellung sein? Oder ist diese erste Aussage technisch korrekt, aber in Wahrheit lautet der nicht-irrtümliche Begriff: "read ist der Befehl zum Lesen von Wörtern aus einer Zeile. Weil er so mächtig ist, können Sie damit Zeilen aus einer Datei lesen, indem Sie Folgendes tun:IFS= read -r line
"Die Theorie
Es gibt zwei Konzepte, die hier im Spiel sind:
IFS
ist das Eingabefeld-Trennzeichen, dh die gelesene Zeichenfolge wird anhand der Zeichen in geteiltIFS
. In einer Befehlszeile werdenIFS
normalerweise Leerzeichen verwendet. Aus diesem Grund wird die Befehlszeile in Leerzeichen aufgeteilt.VAR=value command
"Ändern Sie die Befehlsumgebung so, dassVAR
sie den Wertvalue
" hat. Grundsätzlich wird der Befehlcommand
wird sehen ,VAR
wie mit dem Wertvalue
, aber jeder Befehl ausgeführt wird , dass nach wie vor sehen ,VAR
wie mit seinem vorherigen Wert. Mit anderen Worten, diese Variable wird nur für diese Anweisung geändert.In diesem Fall
Wenn
IFS= read -r line
Sie alsoIFS
eine leere Zeichenfolge festlegen (zum Teilen wird kein Zeichen verwendet, daher erfolgt keine Aufteilung), sodassread
die gesamte Zeile gelesen und als ein Wort angezeigt wird, das derline
Variablen zugewiesen wird. Die Änderungen wirken sichIFS
nur auf diese Anweisung aus, sodass nachfolgende Befehle von der Änderung nicht betroffen sind.Als Anmerkung
Während der Befehl korrekt ist , und wird wie vorgesehen, Einstellung
IFS
in diesem Fallist nichtMacht 1 nicht notwendig. Wie in derbash
Manpage imread
eingebauten Abschnitt geschrieben:Da Sie nur die
line
Variable haben, wird ihr ohnehin jedes Wort zugewiesen. Wenn Sie also keines der vorhergehenden und nachfolgenden Leerzeichen 1 benötigen, können Sie einfach schreibenread -r line
und damit fertig sein.[1] Nur als Beispiel dafür , wie ein
unset
oder$IFS
Standardwert verursachtread
Vorder- / Hinter betrachten IFS Leerzeichen , könnten Sie versuchen:Wenn Sie
IFS
es ausführen, werden Sie feststellen, dass die vorhergehenden und nachfolgenden Zeichen nicht überleben, wenn sie nicht gesetzt sind. Außerdem können einige seltsame Dinge passieren, wenn$IFS
sie irgendwo früher im Skript geändert werden.quelle
Sie sollten diese Aussage in zwei Teilen lesen, der erste , der den Wert des IFS - Variable löscht, dh auf den lesbaren äquivalent ist
IFS=""
, wird die zweiten die Lesevariablenline
von stdin,read -r line
.Was in dieser Syntax speziell ist, ist, dass die IFS-Beeinflussung nur für den
read
Befehl gültig ist .Wenn ich nichts verpasse, hat das Löschen in diesem speziellen FallIFS
keine Auswirkung. Wie auch immerIFS
eingestellt, wird die gesamte Zeile in derline
Variablen gelesen . Eine Verhaltensänderung wäre nur dann eingetreten, wenn mehr als eine Variable als Parameter an dieread
Anweisung übergeben worden wäre.Bearbeiten:
Das
-r
soll ermöglichen, dass Eingaben, die mit enden,\
nicht speziell verarbeitet werden, dh dass der Backslash in derline
Variablen enthalten ist und nicht als Fortsetzungszeichen, um mehrzeilige Eingaben zu ermöglichen.Das Löschen von IFS hat den Nebeneffekt, dass das Lesen verhindert, dass potenzielle führende und nachfolgende Leerzeichen oder Tabulatorzeichen abgeschnitten werden, z.
Vielen Dank an rici für den Hinweis auf diesen Unterschied.
quelle
read -r line
wird, führende und nachfolgende Leerzeichen abgeschnitten werden, bevor die Eingabe derline
Variablen zugewiesen wird.IFS= read a b <<< 'aa bb' ; echo "-$a-$b-"
wird zeigen-aa bb--