wake-up-neo.com

Warum sollten Textdateien mit einem Zeilenumbruch enden?

Ich gehe davon aus, dass jeder hier mit dem Sprichwort vertraut ist, dass alle Textdateien mit einem Zeilenumbruch enden sollten. Ich kenne diese "Regel" seit Jahren, habe mich aber immer gefragt - warum?

1317
Will Robertson

Denn das ist wie der POSIX-Standard eine Zeile definiert :

3.206 Zeile
Eine Folge von null oder mehr Nicht- <newline> -Zeichen plus einem abschließenden <newline> -Zeichen.

Daher werden Zeilen, die nicht mit einem Zeilenumbruch enden, nicht als tatsächliche Zeilen betrachtet. Das ist der Grund, warum einige Programme Probleme haben, die letzte Zeile einer Datei zu verarbeiten, wenn diese nicht durch einen Zeilenumbruch beendet wird.

Bei der Arbeit an einem Terminalemulator hat diese Richtlinie mindestens einen entscheidenden Vorteil: Alle Unix-Tools erwarten diese Konvention und arbeiten damit. Wenn zum Beispiel Dateien mit cat verkettet werden, hat eine durch newline abgeschlossene Datei einen anderen Effekt als eine Datei ohne:

$more a.txt
foo
$more b.txt
bar$more c.txt
baz
$cat {a,b,c}.txt
foo
barbaz

Und wie das vorherige Beispiel ebenfalls zeigt, führt eine Datei mit Zeilenende zu einer korrekten Anzeige, wenn die Datei in der Befehlszeile angezeigt wird (z. B. über more). Eine falsch terminierte Datei ist möglicherweise verstümmelt (zweite Zeile).

Aus Gründen der Konsistenz ist es sehr hilfreich, diese Regel zu befolgen. Andernfalls ist beim Umgang mit den Standard-Unix-Tools zusätzlicher Aufwand erforderlich.


Überlegen Sie es sich anders: Wenn Zeilen nicht durch Zeilenumbrüche abgeschlossen werden, ist es viel schwieriger, Befehle wie cat zu verwenden. Wie können Sie einen Befehl erstellen, um Dateien so zu verketten?

  1. damit wird der Anfang jeder Datei in eine neue Zeile gesetzt, was in 95% der Fälle der Fall sein soll. aber
  2. es ermöglicht das Zusammenführen der letzten und ersten Zeile zweier Dateien, wie im obigen Beispiel zwischen b.txt und c.txt?

Natürlich ist dies lösbar , aber Sie müssen die Verwendung von cat komplexer gestalten (durch Hinzufügen von Positionsbefehlszeilenargumenten, z. B. cat a.txt --no-newline b.txt c.txt) und jetzt Der Befehl steuert nicht jede einzelne Datei, wie sie zusammen mit anderen Dateien eingefügt wird. Dies ist mit ziemlicher Sicherheit nicht bequem.

… Oder Sie müssen ein spezielles Sentinel-Zeichen einführen, um eine Zeile zu markieren, die fortgesetzt und nicht beendet werden soll. Nun, jetzt stecken Sie in der gleichen Situation wie auf POSIX fest, außer invertiert (Zeilenfortsetzung statt Zeilenendezeichen).


Nun, auf nicht POSIX-kompatiblen Systemen (heutzutage meist Windows) ist der Punkt umstritten: Dateien enden im Allgemeinen nicht mit einem Zeilenumbruch und der (informellen) Definition einer Zeile Dies kann beispielsweise "Text sein, der durch Zeilenumbrüche getrennt ist " (Hervorhebung beachten). Dies ist völlig gültig. Bei strukturierten Daten (z. B. Programmcode) wird das Parsen jedoch minimal komplizierter: Im Allgemeinen bedeutet dies, dass Parser neu geschrieben werden müssen. Wenn ein Parser ursprünglich unter Berücksichtigung der POSIX-Definition geschrieben wurde, ist es möglicherweise einfacher, den Token-Stream als den Parser zu ändern. Fügen Sie also am Ende der Eingabe ein "künstliches Newline" -Token hinzu.

1242
Konrad Rudolph

Jede Zeile sollte mit einem Zeilenumbruch abgeschlossen werden, einschließlich der letzten. Einige Programme haben Probleme bei der Verarbeitung der letzten Zeile einer Datei, wenn diese nicht durch einen Zeilenumbruch beendet wird.

GCC warnt nicht, weil es die Datei nicht verarbeiten kann , sondern weil es muss als Teil des Standards.

Der C-Sprachstandard besagt, dass eine Quelldatei, die nicht leer ist, mit einem Zeilenumbruch endet, dem kein umgekehrter Schrägstrich unmittelbar vorausgehen darf.

Da es sich um eine "shall" -Klausel handelt, müssen wir eine Diagnosemeldung für einen Verstoß gegen diese Regel ausgeben.

Dies ist in Abschnitt 2.1.1.2 des ANSI C 1989-Standards beschrieben. Abschnitt 5.1.1.2 der Norm ISO C 1999 (und wahrscheinlich auch der Norm ISO C 1990).

Hinweis: Das GCC/GNU Mail-Archiv .

263
Bill the Lizard

Diese Antwort ist eher ein Versuch einer technischen Antwort als einer Meinung.

Wenn wir POSIX-Puristen sein wollen, definieren wir eine Linie als:

Eine Folge von null oder mehr Nicht- <newline> -Zeichen plus einem abschließenden <newline> -Zeichen.

Quelle: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206

Eine unvollständige Zeile als:

Eine Folge von mindestens einem Nicht-Newline-Zeichen am Ende der Datei.

Quelle: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195

Eine Textdatei als:

Eine Datei, die Zeichen enthält, die in null oder mehr Zeilen angeordnet sind. Die Zeilen enthalten keine NUL-Zeichen und keine darf länger als {LINE_MAX} Byte sein, einschließlich des Zeichens <newline>. Obwohl POSIX.1-2008 nicht zwischen Textdateien und Binärdateien unterscheidet (siehe ISO C-Standard), liefern viele Dienstprogramme nur eine vorhersehbare oder aussagekräftige Ausgabe, wenn sie mit Textdateien arbeiten. Die Standarddienstprogramme, für die solche Einschränkungen gelten, geben in ihren Abschnitten STDIN oder INPUT FILES immer "Textdateien" an.

Quelle: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_397

Eine Zeichenfolge als:

Eine zusammenhängende Folge von Bytes, die mit dem ersten Nullbyte abgeschlossen sind und dieses enthalten.

Quelle: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396

Daraus können wir dann ableiten, dass das einzige Mal, wenn wir uns mit dem Konzept einer Zeile einer Datei oder einer Datei als möglicherweise befassen, Probleme jeglicher Art auftreten werden a Textdatei (da eine Textdatei eine Organisation aus null oder mehr Zeilen ist und eine Zeile, die wir kennen, mit einem <newline> enden muss).

Beispiel: wc -l filename.

Aus dem Handbuch von wc lesen wir:

Eine Zeile ist eine Zeichenfolge, die durch ein <newline> -Zeichen begrenzt wird.

Was bedeutet es dann für JavaScript-, HTML- und CSS-Dateien, dass es sich um text ​​-Dateien handelt?

In Browsern, modernen IDEs und anderen Front-End-Anwendungen gibt es keine Probleme beim Überspringen von EOL bei EOF. Die Anwendungen analysieren die Dateien ordnungsgemäß. Da nicht alle Betriebssysteme dem POSIX-Standard entsprechen, ist es für Nicht-OS-Tools (z. B. Browser) unpraktisch, Dateien gemäß dem POSIX-Standard (oder einem Standard auf Betriebssystemebene) zu verarbeiten.

Daher können wir relativ sicher sein, dass EOL auf EOF auf Anwendungsebene praktisch keine negativen Auswirkungen hat - unabhängig davon, ob es unter einem UNIX-Betriebssystem ausgeführt wird.

An dieser Stelle können wir mit Zuversicht sagen, dass das Überspringen von EOL bei EOF sicher ist, wenn es sich auf der Clientseite um JS, HTML und CSS handelt. Tatsächlich können wir feststellen, dass das Minimieren einer dieser Dateien, die kein <newline> enthalten, sicher ist.

Wir können noch einen Schritt weiter gehen und sagen, dass auch NodeJS nicht dem POSIX-Standard entsprechen kann, da es in nicht POSIX-kompatiblen Umgebungen ausgeführt werden kann.

Was bleibt uns dann übrig? Werkzeuge auf Systemebene.

Dies bedeutet, dass die einzigen Probleme auftreten können, die sich bemühen, ihre Funktionalität an die Semantik von POSIX anzupassen (z. B. Definition einer Linie wie in wc gezeigt).

Trotzdem haften nicht alle Shells automatisch an POSIX. Bash zum Beispiel verwendet standardmäßig nicht das POSIX-Verhalten. Es gibt einen Schalter, um es zu aktivieren: POSIXLY_CORRECT.

Denkanstoß für den Wert von EOL als <newline>: https://www.rfc-editor.org/old/EOLstory.txt

Wenn Sie praktisch auf dem neuesten Stand der Technik bleiben, sollten Sie Folgendes berücksichtigen:

Lassen Sie uns mit einer Datei ohne EOL arbeiten. Zum jetzigen Zeitpunkt ist die Datei in diesem Beispiel ein minimiertes JavaScript ohne EOL.

curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o x.js
curl http://cdnjs.cloudflare.com/ajax/libs/AniJS/0.5.0/anijs-min.js -o y.js

$ cat x.js y.js > z.js

-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 x.js
-rw-r--r--  1 milanadamovsky   7905 Aug 14 23:17 y.js
-rw-r--r--  1 milanadamovsky  15810 Aug 14 23:18 z.js

Beachten Sie, dass die Dateigröße cat genau die Summe der einzelnen Teile ist. Wenn die Verkettung von JavaScript-Dateien für JS-Dateien von Belang ist, ist es sinnvoller, jede JavaScript-Datei mit einem Semikolon zu starten.

Als jemand anderes in diesem Thread erwähnt: Was ist, wenn Sie zwei Dateien cat wollen, deren Ausgabe nur eine Zeile statt zwei wird? Mit anderen Worten, cat tut, was es tun soll.

Das man von cat erwähnt nur Leseeingaben bis EOF, nicht <newline>. Beachten Sie, dass der Schalter -n von cat auch eine nicht mit <newline> abgeschlossene Zeile (oder nvollständige Zeile) als Zeile - ausgibt. wobei die Zählung bei 1 beginnt (entsprechend dem man)

-n Nummeriert die Ausgabezeilen, beginnend mit 1.

Nachdem wir verstanden haben, wie POSIX ein Zeile definiert, wird dieses Verhalten mehrdeutig oder wirklich nicht konform.

Wenn Sie den Zweck und die Konformität eines bestimmten Tools verstehen, können Sie feststellen, wie wichtig es ist, Dateien mit einer EOL zu beenden. In C, C++, Java (JARs), etc ... schreiben einige Standards einen Zeilenumbruch für die Gültigkeit vor. Für JS, HTML und CSS gibt es keinen solchen Standard.

Anstatt wc -l filename zu verwenden, könnte man awk '{x++}END{ print x}' filename ausführen und sicher sein, dass der Erfolg der Aufgabe nicht durch eine Datei gefährdet wird, die wir möglicherweise verarbeiten möchten, die wir nicht geschrieben haben (z. B. eine Bibliothek eines Drittanbieters, z als minimiertes JS curld) - es sei denn, wir wollten wirklich lines im Sinne der POSIX-Konformität zählen.

Fazit

Es wird nur sehr wenige reale Anwendungsfälle geben, in denen das Überspringen von EOL bei EOF für bestimmte Textdateien wie JS, HTML und CSS - wenn überhaupt - negative Auswirkungen hat. Wenn wir uns darauf verlassen, dass <newline> vorhanden ist, beschränken wir die Zuverlässigkeit unserer Tools nur auf die Dateien, die wir erstellen, und öffnen uns für potenzielle Fehler, die durch Dateien von Drittanbietern verursacht werden.

Die Moral der Geschichte: Werkzeuge für Ingenieure, die nicht die Schwäche haben, sich auf EOL bei EOF zu verlassen.

Sie können auch Anwendungsfälle veröffentlichen, die für JS, HTML und CSS gelten. Dort können Sie untersuchen, wie sich das Überspringen von EOL nachteilig auswirkt.

105
Milan Adamovsky

Dies kann mit dem nterschied zwischen zusammenhängen:

  • textdatei (jede Zeile soll in einem Zeilenende enden)
  • binärdatei (es gibt keine echten "Zeilen" und die Länge der Datei muss erhalten bleiben)

Wenn jede Zeile mit einem Zeilenende endet, wird beispielsweise vermieden, dass durch die Verknüpfung zweier Textdateien die letzte Zeile der ersten Zeile in die erste Zeile der zweiten Zeile übergeht.

Außerdem kann ein Editor beim Laden prüfen, ob die Datei am Ende der Zeile endet, sie in der lokalen Option 'eol' speichern und diese beim Schreiben der Datei verwenden.

Vor ein paar Jahren (2005) haben viele Redakteure (ZDE, Eclipse, Scite, ...) diese endgültige EOL "vergessen", was nicht sehr geschätzt wurde .
Nicht nur das, sondern sie interpretierten diese endgültige EOL falsch als 'eine neue Zeile beginnen' und fingen tatsächlich an, eine andere Zeile anzuzeigen, als ob sie bereits existiert hätte.
Dies war bei einer 'richtigen' Textdatei mit einem anständigen Texteditor wie vim sehr gut sichtbar, verglichen mit dem Öffnen in einem der oben genannten Editoren. Es wurde eine zusätzliche Zeile unter der eigentlichen letzten Zeile der Datei angezeigt. Sie sehen so etwas:

1 first line
2 middle line
3 last line
4
60
VonC

Einige Tools erwarten dies. Zum Beispiel erwartet wc Folgendes:

$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
40
Flimm

Grundsätzlich gibt es viele Programme, die Dateien nicht korrekt verarbeiten, wenn sie nicht das endgültige EOL EOF erhalten.

GCC warnt Sie davor, da dies als Teil des C-Standards erwartet wird. (Abschnitt 5.1.1.2 anscheinend)

Compiler-Warnung "Keine neue Zeile am Ende der Datei"

19
cgp

Ein separater Anwendungsfall: Wenn Ihre Textdatei versionskontrolliert ist (in diesem Fall speziell unter git, obwohl dies auch für andere gilt). Wenn am Ende der Datei Inhalt hinzugefügt wird, wurde die Zeile, die zuvor die letzte Zeile war, so bearbeitet, dass sie ein Zeilenumbruchzeichen enthält. Dies bedeutet, dass blameWenn Sie in der Datei nachsehen, wann diese Zeile zuletzt bearbeitet wurde, wird der hinzugefügte Text und nicht das zuvor von Ihnen gewünschte Commit angezeigt.

13

Dies stammt aus den frühen Tagen, als einfache Terminals verwendet wurden. Mit dem Zeilenumbruchzeichen wurde ein 'Flush' der übertragenen Daten ausgelöst.

Heute wird der Zeilenumbruch nicht mehr benötigt. Sicher, viele Apps haben immer noch Probleme, wenn der Zeilenumbruch nicht vorhanden ist, aber ich würde das als Fehler in diesen Apps ansehen.

Wenn Sie jedoch ein Textdateiformat haben, in dem Sie erfordern die neue Zeile, erhalten Sie einfache Datenüberprüfung sehr billig: Wenn die Datei mit einer Zeile endet, die am Ende keine neue Zeile enthält, wissen Sie, dass die Datei defekt ist . Mit nur einem zusätzlichen Byte für jede Zeile können Sie defekte Dateien mit hoher Genauigkeit und nahezu ohne CPU-Zeit erkennen.

12
Stefan

Zusätzlich zu den oben genannten praktischen Gründen würde es mich nicht überraschen, wenn die Urheber von Unix (Thompson, Ritchie, et al.) Oder deren Multics-Vorgängern erkannten, dass es einen theoretischen Grund gibt, Zeilenendezeichen anstelle von Zeilentrennzeichen zu verwenden: Mit Zeile Abschlusszeichen, können Sie alle möglichen Dateien von Zeilen codieren. Bei Zeilentrennzeichen gibt es keinen Unterschied zwischen einer Datei mit Nullzeilen und einer Datei mit einer einzelnen Leerzeile. Beide sind als Datei mit null Zeichen codiert.

Die Gründe dafür sind:

  1. Denn so definiert es POSIX.
  2. Weil einige Tools es erwarten oder sich ohne es "schlecht benehmen". Beispielsweise zählt wc -l keine letzte "Zeile", wenn sie nicht mit einer neuen Zeile endet.
  3. Weil es einfach und bequem ist. Unter Unix funktioniert cat einfach und ohne Komplikationen. Es werden nur die Bytes jeder Datei kopiert, ohne dass eine Interpretation erforderlich ist. Ich glaube nicht, dass es ein DOS-Äquivalent zu cat gibt. Bei Verwendung von copy a+b c wird die letzte Zeile der Datei a mit der ersten Zeile der Datei b zusammengeführt.
  4. Weil eine Datei (oder ein Stream) mit Nullzeilen von einer Datei mit einer Leerzeile unterschieden werden kann.
11
John Wiersba

Vermutlich einfach, dass irgendein Parsing-Code damit gerechnet hat.

Ich bin mir nicht sicher, ob ich es für eine "Regel" halten würde, und ich halte mich auch religiös nicht daran. Der sinnvollste Code weiß, wie Text (einschließlich Codierungen) zeilenweise (mit oder ohne Zeilenumbruch in der letzten Zeile) analysiert wird.

In der Tat - wenn Sie mit einer neuen Zeile enden: Gibt es (theoretisch) eine leere letzte Zeile zwischen der EOL und der EOF? Eins zum Nachdenken ...

10
Marc Gravell

Es gibt auch ein praktisches Programmierproblem mit Dateien, denen am Ende Zeilenumbrüche fehlen: Die read Bash-Funktion (ich weiß nicht, wie andere read Implementierungen funktionieren) funktioniert nicht wie erwartet:

printf $'foo\nbar' | while read line
do
    echo $line
done

Dies druckt nur foo! Der Grund ist, dass wenn read auf die letzte Zeile trifft, der Inhalt in $line geschrieben wird, aber der Exit-Code 1 zurückgegeben wird, weil er EOF erreicht hat. Dies unterbricht die while -Schleife, sodass wir niemals den echo $line -Teil erreichen. Wenn Sie mit dieser Situation umgehen möchten, müssen Sie Folgendes tun:

while read line || [ -n "${line-}" ]
do
    echo $line
done < <(printf $'foo\nbar')

Das heißt, führen Sie die echo aus, wenn die read aufgrund einer nicht leeren Zeile am Ende der Datei fehlgeschlagen ist. In diesem Fall gibt es natürlich eine zusätzliche Zeile in der Ausgabe, die nicht in der Eingabe enthalten war.

10
l0b0

Ich habe mich das jahrelang selbst gefragt. Aber ich bin heute auf einen guten Grund gestoßen.

Stellen Sie sich eine Datei mit einem Datensatz in jeder Zeile vor (z. B. eine CSV-Datei). Und dass der Computer am Ende der Datei Aufzeichnungen schrieb. Aber es stürzte plötzlich ab. Gee war die letzte Zeile komplett? (keine schöne Situation)

Wenn wir jedoch immer die letzte Zeile beenden, wissen wir Bescheid (prüfen Sie einfach, ob die letzte Zeile beendet ist). Andernfalls müssten wir wahrscheinlich jedes Mal die letzte Zeile verwerfen, um sicher zu gehen.

9
symbiont

Warum sollten (Text-) Dateien mit einem Zeilenumbruch enden?

Wie auch von vielen zum Ausdruck gebracht, weil:

  1. Viele Programme verhalten sich nicht gut oder scheitern ohne.

  2. Selbst Programmen, die eine Datei gut verarbeiten, fehlt die Endung '\n'. Die Funktionalität des Tools entspricht möglicherweise nicht den Erwartungen des Benutzers - was in diesem Eckfall unklar sein kann.

  3. Programme selten disallow final '\n' (ich kenne keine).


Dies wirft jedoch die nächste Frage auf:

Was sollte Code mit Textdateien ohne Zeilenumbruch tun?

  1. Das Wichtigste - Schreiben Sie keinen Code, der davon ausgeht, dass eine Textdatei mit einer neuen Zeile endet.. Angenommen Eine Datei, die einem Format entspricht, führt zu Datenbeschädigung, Hackerangriffen und Abstürzen. Beispiel:

    // Bad code
    while (fgets(buf, sizeof buf, instream)) {
      // What happens if there is no \n, buf[] is truncated leading to who knows what
      buf[strlen(buf) - 1] = '\0';  // attempt to rid trailing \n
      ...
    }
    
  2. Wenn der letzte abschließende '\n' benötigt wird, benachrichtigen Sie den Benutzer über seine Abwesenheit und die durchgeführten Maßnahmen. IOWs, überprüfen Sie das Format der Datei. Hinweis: Dies kann eine Begrenzung der maximalen Zeilenlänge, Zeichencodierung usw. beinhalten.

  3. Definieren Sie klar, dokumentieren Sie, wie der Code mit einem fehlenden finalen '\n' umgeht.

  4. Verwenden Sie nach Möglichkeit nicht generate eine Datei, der die Endung '\n' fehlt.

8
chux

Es ist sehr spät hier, aber ich hatte gerade einen Fehler in der Dateiverarbeitung und das kam, weil die Dateien nicht mit einem leeren Zeilenumbruch endeten. Wir haben Textdateien mit sed verarbeitet, und sed hat die letzte Zeile in der Ausgabe weggelassen, was dazu führte, dass eine ungültige JSON-Struktur auftrat und der Rest des Prozesses fehlschlug.

Alles was wir gemacht haben war:

Es gibt eine Beispieldatei, die beispielsweise Folgendes enthält: foo.txt mit etwas json Inhalt.

[{
    someProp: value
},
{
    someProp: value
}] <-- No newline here

Die Datei wurde auf einem Windows-Computer erstellt, und Fensterskripte verarbeiteten diese Datei mit Hilfe von Powershall-Befehlen. Alles gut.

Bei der Verarbeitung derselben Datei mit dem Befehl sedsed 's|value|newValue|g' foo.txt > foo.txt.tmp wurde die neu generierte Datei erstellt

[{
    someProp: value
},
{
    someProp: value

und boom, es ist den Rest der Prozesse wegen der ungültigen JSON fehlgeschlagen.

Es ist daher immer ratsam, Ihre Datei mit einer leeren neuen Zeile zu beenden.

3
Arpit

Ich hatte immer den Eindruck, dass die Regel aus der Zeit stammt, als das Parsen einer Datei ohne abschließenden Zeilenumbruch schwierig war. Das heißt, Sie würden am Ende Code schreiben, bei dem ein Zeilenende durch das EOL-Zeichen oder EOF definiert wurde. Es war einfach anzunehmen, dass eine Zeile mit EOL endet.

Ich glaube jedoch, dass die Regel von C-Compilern abgeleitet ist, die die neue Zeile benötigen. Und wie auf Compiler-Warnung "Keine neue Zeile am Ende der Datei" hingewiesen, fügt #include keine neue Zeile hinzu.

3
he_the_great

Stellen Sie sich vor, die Datei wird verarbeitet, während sie noch von einem anderen Prozess generiert wird.

Es könnte damit zu tun haben? Ein Flag, das angibt, dass die Datei zur Verarbeitung bereit ist.

0
Pippen_001