Antworten:
Die Falle ist das
IFS=; while read..
Legt die IFS
für die gesamte Shell-Umgebung außerhalb der Schleife fest, wohingegen
while IFS= read
Definiert es nur für den read
Aufruf neu (außer in der Bourne-Shell). Sie können das überprüfen, indem Sie eine Schleife wie
while IFS= read xxx; ... done
Dann wird nach einer solchen Schleife echo "blabalbla $IFS ooooooo"
gedruckt
blabalbla
ooooooo
während nach
IFS=; read xxx; ... done
die IFS
bleibt neu definiert: jetzt echo "blabalbla $IFS ooooooo"
druckt
blabalbla ooooooo
Also , wenn Sie das zweite Formular zu verwenden, müssen Sie zurücksetzen erinnern: IFS=$' \t\n'
.
Der zweite Teil dieser Frage wurde hier zusammengeführt , daher habe ich die zugehörige Antwort hier entfernt.
while
nicht viel Sinn macht - die Bedingung für die while
Enden an diesem Semikolon, so gibt es keine eigentliche Schleife ... read
wird nur der erste Befehl in der einelementige Schleife ... Oder auch nicht ? Was ist mit dem do
dann ..?
while
Bedingung haben (vorher do
).
IFS=
, aber IFS=X
nicht ... (oder vielleicht habe ich eine Weile darüber nachgedacht .. Kaffeepause benötigt :)
Schauen wir uns ein Beispiel mit einem sorgfältig ausgearbeiteten Eingabetext an:
text=' hello world\
foo\bar'
Das sind zwei Zeilen, wobei die erste mit einem Leerzeichen beginnt und mit einem Backslash endet. Schauen wir uns zunächst an, was passiert, ohne dass Vorsichtsmaßnahmen getroffen werden müssen read
(aber printf '%s\n' "$text"
um vorsichtig zu drucken, $text
ohne dass die Gefahr einer Erweiterung besteht). (Unten sehen Sie $
die Shell-Eingabeaufforderung.)
$ printf '%s\n' "$text" |
while read line; do printf '%s\n' "[$line]"; done
[hello worldfoobar]
read
aßen die Backslashes auf: backslash-newline bewirkt, dass die Newline ignoriert wird, und backslash-anything ignoriert diesen ersten Backslash. Um zu vermeiden, dass Backslashes speziell behandelt werden, verwenden wir read -r
.
$ printf '%s\n' "$text" |
while read -r line; do printf '%s\n' "[$line]"; done
[hello world\]
[foo\bar]
Das ist besser, wir haben wie erwartet zwei Zeilen. Die beiden Zeilen enthalten fast den gewünschten Inhalt: Der doppelte Abstand zwischen hello
und world
wurde beibehalten, da er sich innerhalb der line
Variablen befindet. Auf der anderen Seite wurde der anfängliche Platz aufgefressen. Das liegt daran, dass read
so viele Wörter gelesen werden, wie Sie Variablen übergeben, mit der Ausnahme, dass die letzte Variable den Rest der Zeile enthält - aber sie beginnt immer noch mit dem ersten Wort, dh die anfänglichen Leerzeichen werden verworfen.
Um also jede Zeile buchstäblich lesen zu können, müssen wir sicherstellen, dass keine Wortspaltung stattfindet . Dazu setzen wir die IFS
Variable auf einen leeren Wert.
$ printf '%s\n' "$text" |
while IFS= read -r line; do printf '%s\n' "[$line]"; done
[ hello world\]
[foo\bar]
Beachten Sie, wie wir IFS
speziell für die Dauer des read
eingebauten einstellen . Das IFS= read -r line
setzt die Umgebungsvariable IFS
(auf einen leeren Wert) speziell für die Ausführung von read
. Dies ist eine Instanz der allgemeinen einfachen Befehlssyntax : Eine (möglicherweise leere) Folge von Variablenzuweisungen, gefolgt von einem Befehlsnamen und seinen Argumenten (Sie können auch an jeder Stelle Umleitungen einfügen). Da read
es sich um eine integrierte Variable handelt, gelangt die Variable nie in die Umgebung eines externen Prozesses. nichtsdestotrotz ist der Wert von $IFS
das, was wir dort zuweisen, solange read
es ausgeführt wird¹. Beachten Sie, dass dies read
keine spezielle integrierte Funktion ist , sodass die Zuweisung nur für ihre Dauer gültig ist.
Daher achten wir darauf, den Wert von nicht IFS
für andere Anweisungen zu ändern, die möglicherweise darauf angewiesen sind. Dieser Code funktioniert unabhängig von der IFS
ursprünglichen Einstellung des umgebenden Codes und verursacht keine Probleme, wenn der Code in der Schleife darauf angewiesen ist IFS
.
Vergleichen Sie diesen Codeausschnitt, der Dateien in einem durch Doppelpunkte getrennten Pfad nachschlägt. Die Liste der Dateinamen wird aus einer Datei gelesen, ein Dateiname pro Zeile.
IFS=":"; set -f
while IFS= read -r name; do
for dir in $PATH; do
## At this point, "$IFS" is still ":"
if [ -e "$dir/$name" ]; then echo "$dir/$name"; fi
done
done <filenames.txt
Wenn die Schleife wäre while IFS=; read -r name; do …
, for dir in $PATH
würde sie nicht $PATH
in durch Doppelpunkte getrennte Komponenten aufgeteilt. Wenn der Code IFS=; while read …
wäre, wäre es noch offensichtlicher, dass im Schleifenkörper IFS
nicht festgelegt :
ist.
Natürlich wäre es möglich, den Wert von IFS
nach der Ausführung wiederherzustellen read
. Dafür müsste man jedoch den vorherigen Wert kennen, was ein zusätzlicher Aufwand ist. IFS= read
ist der einfache Weg (und bequemerweise auch der kürzeste Weg).
¹ Und wenn read
dies durch ein überfülltes Signal unterbrochen wird, möglicherweise während die Überfüllung ausgeführt wird - dies wird von POSIX nicht angegeben und hängt von der Shell in der Praxis ab.
while IFS= read
(ohne ein Semikolon danach =
) keine spezielle Form von while
oder von IFS
oder von ist read
. Das Konstrukt ist generisch: dh. anyvar=anyvalue anycommand
. Das Fehlen von ;
after setting anyvar
macht den Gültigkeitsbereich von anyvar
local zu anycommand
. Die while-do / done-Schleife ist zu 100% unabhängig vom lokalen Gültigkeitsbereich von any_var
.
Abgesehen von der (bereits geklärt) IFS
Scoping Unterschiede zwischen dem while IFS='' read
, IFS=''; while read
und while IFS=''; read
Idiomen (pro-Befehl vs / script Shell weiter IFS
Variable Scoping), die Take-Home - Lehre ist , dass Sie die führenden verlieren und Leerzeichen am Ende einer Eingabezeile , wenn das IFS - Variable ist auf (enthält ein) Leerzeichen gesetzt.
Dies kann schwerwiegende Folgen haben, wenn Dateipfade verarbeitet werden.
Daher ist das Setzen der IFS-Variablen auf die leere Zeichenfolge alles andere als eine schlechte Idee, da hierdurch sichergestellt wird, dass das führende und nachfolgende Leerzeichen einer Zeile nicht entfernt wird.
Siehe auch: Bash, Zeile für Zeile aus Datei lesen, mit IFS
(
shopt -s nullglob
touch ' file with spaces '
IFS=$' \t\n' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
IFS='' read -r file <<<"$(printf '%s' *file*with*spaces*)"
ls -l "$file"
)
Inspiriert von Yuzems Antwort
Wenn Sie IFS
einen tatsächlichen Charakter festlegen möchten , funktionierte dies für mich
iconv -f cp1252 zapni.tv.php | while IFS='#' read -d'#' line
do
echo "$line"
done
while IFS=X read
teilt sich nicht umX
,while IFS=X; read
tut es aber ...