Der C-Standard besagt, dass Textdateien mit einem Zeilenumbruch enden müssen, da sonst die Daten nach dem letzten Zeilenumbruch möglicherweise nicht richtig gelesen werden.
ISO / IEC 9899: 2011 §7.21.2 Streams
Ein Textstrom ist eine geordnete Folge von Zeichen, die zu Zeilen zusammengesetzt sind. Jede Zeile besteht aus null oder mehr Zeichen plus einem abschließenden Zeichen für neue Zeilen. Ob für die letzte Zeile ein abschließendes Zeichen für eine neue Zeile erforderlich ist, ist implementierungsdefiniert. Bei der Eingabe und Ausgabe müssen möglicherweise Zeichen hinzugefügt, geändert oder gelöscht werden, um den unterschiedlichen Konventionen für die Darstellung von Text in der Hostumgebung zu entsprechen. Daher muss es keine Eins-zu-Eins-Entsprechung zwischen den Zeichen in einem Stream und denen in der externen Darstellung geben. Aus einem Textstrom eingelesene Daten werden notwendigerweise nur dann mit den Daten verglichen, die zuvor in diesen Strom geschrieben wurden, wenn: die Daten nur aus Druckzeichen und der horizontalen Registerkarte und der neuen Zeile der Steuerzeichen bestehen; Vor keinem Zeilenumbruchzeichen stehen Leerzeichen. und das letzte Zeichen ist ein Zeilenumbruchzeichen. Ob Leerzeichen, die unmittelbar vor einem Zeilenumbruch ausgeschrieben werden, beim Einlesen angezeigt werden, ist implementierungsdefiniert.
Ich hätte nicht unerwartet einen fehlenden Zeilenumbruch am Ende der Datei, der Probleme in bash
(oder einer Unix-Shell) verursachen könnte, aber das scheint das Problem reproduzierbar zu sein ( $
ist die Eingabeaufforderung in dieser Ausgabe):
$ echo xxx\\c
xxx$ { echo abc; echo def; echo ghi; echo xxx\\c; } > y
$ cat y
abc
def
ghi
xxx$
$ while read line; do echo $line; done < y
abc
def
ghi
$ bash -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ ksh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ zsh -c 'while read line; do echo $line; done < y'
abc
def
ghi
$ for line in $(<y); do echo $line; done
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done
abc
def
ghi
xxx
$
Es ist auch nicht auf bash
- Korn shell ( ksh
) beschränkt und zsh
verhält sich auch so. Ich lebe, ich lerne; Vielen Dank, dass Sie das Problem angesprochen haben.
Wie im obigen Code gezeigt, cat
liest der Befehl die gesamte Datei. Die for line in `cat $DATAFILE`
Technik sammelt alle Ausgaben und ersetzt beliebige Sequenzen von Leerzeichen durch ein einzelnes Leerzeichen (ich schließe daraus, dass jede Zeile in der Datei keine Leerzeichen enthält).
Getestet unter Mac OS X 10.7.5.
Was sagt POSIX?
Die POSIX- read
Befehlsspezifikation lautet:
Das Lese-Dienstprogramm liest eine einzelne Zeile von der Standardeingabe.
Sofern die -r
Option nicht angegeben ist, fungiert <backslash> standardmäßig als Escape-Zeichen. Ein nicht entkoppelter <backslash> behält den Literalwert des folgenden Zeichens bei, mit Ausnahme eines <newline>. Wenn ein <newline> dem <backslash> folgt, interpretiert das Dienstprogramm read dies als Zeilenfortsetzung. Der <Backslash> und <newline>
muss entfernt werden, bevor die Eingabe in Felder aufgeteilt wird. Alle anderen nicht entkoppelten <Backslash> -Zeichen werden entfernt, nachdem die Eingabe in Felder aufgeteilt wurde.
Wenn es sich bei der Standardeingabe um ein Endgerät handelt und die aufrufende Shell interaktiv ist, fordert read beim Lesen einer Eingabezeile, die mit einem <backlash> <newline> endet, eine Fortsetzungszeile auf, sofern die -r
Option nicht angegeben ist.
Die abschließende <newline> (falls vorhanden) wird aus der Eingabe entfernt und die Ergebnisse werden wie in der Shell für die Ergebnisse der Parametererweiterung in Felder aufgeteilt (siehe Feldaufteilung). [...]
Beachten Sie, dass '(falls vorhanden)' (Hervorhebung im Zitat hinzugefügt)! Es scheint mir, dass wenn es keine neue Zeile gibt, es immer noch das Ergebnis lesen sollte. Auf der anderen Seite heißt es auch:
STDIN
Die Standardeingabe ist eine Textdatei.
und dann kehren Sie zur Debatte zurück, ob eine Datei, die nicht mit einem Zeilenumbruch endet, eine Textdatei ist oder nicht.
Die Begründung auf derselben Seite dokumentiert jedoch:
Obwohl die Standardeingabe eine Textdatei sein muss und daher immer mit einer <neuen Zeile> endet (es sei denn, es handelt sich um eine leere Datei), kann die Verarbeitung von Fortsetzungszeilen, wenn die -r
Option nicht verwendet wird, dazu führen, dass die Eingabe nicht mit endet eine <newline>. Dies tritt auf, wenn die letzte Zeile der Eingabedatei mit einem <backlash> <newline> endet. Aus diesem Grund wird in der Beschreibung "falls vorhanden" in "Die abschließende <newline> (falls vorhanden) aus der Eingabe entfernt" verwendet. Es ist keine Lockerung der Anforderung, dass die Standardeingabe eine Textdatei sein muss.
Diese Begründung muss bedeuten, dass die Textdatei mit einem Zeilenumbruch enden soll.
Die POSIX-Definition einer Textdatei lautet:
3.395 Textdatei
Eine Datei, die Zeichen enthält, die in null oder mehr Zeilen organisiert sind. Die Zeilen enthalten keine NUL-Zeichen und keines darf die Länge von {LINE_MAX} Bytes überschreiten, einschließlich des Zeichens <newline>. Obwohl POSIX.1-2008 nicht zwischen Textdateien und Binärdateien unterscheidet (siehe ISO C-Standard), erzeugen viele Dienstprogramme nur vorhersehbare oder aussagekräftige Ausgaben, wenn sie mit Textdateien arbeiten. Die Standarddienstprogramme mit solchen Einschränkungen geben in ihren Abschnitten STDIN oder INPUT FILES immer "Textdateien" an.
Dies legt nicht fest, dass 'endet mit einer <newline>' direkt, sondern widerspricht dem C-Standard.
Eine Lösung für das Problem "No Terminal Newline"
Hinweis Gordon Davisson ‚s Antwort . Ein einfacher Test zeigt, dass seine Beobachtung korrekt ist:
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
Daher ist seine Technik von:
while read line || [ -n "$line" ]; do echo $line; done < y
oder:
cat y | while read line || [ -n "$line" ]; do echo $line; done
funktioniert für Dateien ohne Zeilenumbruch am Ende (zumindest auf meinem Computer).
Ich bin immer noch überrascht, dass die Shells das letzte Segment (es kann nicht als Zeile bezeichnet werden, da es nicht mit einer neuen Zeile endet) der Eingabe löschen, aber in POSIX gibt es möglicherweise eine ausreichende Begründung dafür. Und natürlich ist es am besten sicherzustellen, dass Ihre Textdateien wirklich Textdateien sind, die mit einem Zeilenumbruch enden.
cat somefile | while read
dass alle in derwhile
Schleife festgelegten Variablen beim Beenden der Schleife zerstört werden. Sie wollen wahrscheinlichwhile read ...; done <somefile
stattdessen; siehe BashFAQ # 24 .