web-dev-qa-db-fra.com

Pourquoi les fichiers texte doivent-ils se terminer par une nouvelle ligne?

Je suppose que tout le monde ici connaît l'adage selon lequel tous les fichiers texte doivent se terminer par une nouvelle ligne. Je connais cette "règle" depuis des années mais je me suis toujours demandé pourquoi?

1274
Will Robertson

Parce que c’est comment le standard POSIX définit une ligne :

3.206 Ligne
Une séquence de zéro ou plusieurs caractères non <nouvelle ligne> plus un caractère <nouvelle ligne> de fin.

Par conséquent, les lignes ne se terminant pas par un caractère de nouvelle ligne ne sont pas considérées comme des lignes réelles. C'est pourquoi certains programmes ont des problèmes pour traiter la dernière ligne d'un fichier s'il n'est pas mis fin à la nouvelle ligne.

Lorsque vous travaillez sur un émulateur de terminal, il existe au moins un avantage indéniable: tous les outils Unix attendent cette convention et fonctionnent avec elle. Par exemple, lors de la concaténation de fichiers avec catname__, un fichier terminé par newline aura un effet différent de celui qui ne contient pas:

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

Et, comme le montre l'exemple précédent, lors de l'affichage du fichier sur la ligne de commande (par exemple, via morename__), un fichier terminé par une nouvelle ligne entraîne un affichage correct. Un fichier mal terminé peut être tronqué (deuxième ligne).

Pour des raisons de cohérence, il est très utile de suivre cette règle - sinon, cela entraînerait un surcroît de travail avec les outils Unix par défaut.


Pensez-y différemment: si les lignes ne sont pas terminées par une nouvelle ligne, il est beaucoup plus difficile d'utiliser des commandes telles que catname__: comment créer une commande pour concaténer des fichiers tels que

  1. chaque dossier est placé sur une nouvelle ligne, comme vous le souhaitez 95% du temps; mais
  2. cela permet de fusionner la dernière et la première ligne de deux fichiers, comme dans l'exemple ci-dessus entre b.txt et c.txt?

Bien sûr, c'est résolvable mais vous devez rendre l'utilisation de catplus complexe (en ajoutant des arguments de ligne de commande, par exemple cat a.txt --no-newline b.txt c.txt), et maintenant la commande plutôt que chaque personne. file contrôle la manière dont il est collé avec d'autres fichiers. Ce n'est certainement pas pratique.

… Ou vous devez introduire un caractère sentinelle spécial pour marquer une ligne censée être poursuivie plutôt que terminée. Eh bien, vous êtes maintenant confronté à la même situation que sur POSIX, à l’inverse de l’inverse (poursuite de la ligne plutôt que caractère de fin de ligne).


Désormais, sur les systèmes non compatibles POSIX (actuellement essentiellement Windows), le problème est sans objet: les fichiers ne se terminent généralement pas par une nouvelle ligne, et la définition (informelle) d'une ligne peut par exemple être " texte qui est séparé par des lignes nouvelles "(notez l’accent). Ceci est entièrement valide. Toutefois, pour les données structurées (par exemple, le code de programmation), l’analyse simplifiée est simplifiée: cela signifie généralement que les analyseurs doivent être réécrits. Si un analyseur a été écrit à l'origine avec la définition POSIX à l'esprit, il serait alors plus facile de modifier le flux de jetons plutôt que l'analyseur. En d'autres termes, ajoutez un jeton "newline artificiel" à la fin de l'entrée.

1204
Konrad Rudolph

Chaque ligne doit être terminée par un caractère de nouvelle ligne, y compris le dernier. Certains programmes rencontrent des problèmes pour traiter la dernière ligne d'un fichier s'il n'est pas mis fin à la nouvelle ligne.

GCC le met en garde non pas parce qu'il ne peut pas traiter le fichier, mais parce qu'il doit dans le cadre de la norme.

La norme de langage C stipule qu'un fichier source non vide doit se terminer par un caractère de nouvelle ligne, qui ne doit pas être immédiatement précédé d'un caractère de barre oblique inverse.

Puisqu'il s'agit d'une clause "doit", nous devons émettre un message de diagnostic en cas de violation de cette règle.

Ceci est dans la section 2.1.1.2 de la norme ANSI C 1989. Section 5.1.1.2 de la norme ISO C 1999 (et probablement aussi de la norme ISO C 1990).

Référence: archive de courrier GCC/GN .

263
Bill the Lizard

Cette réponse est une tentative de réponse technique plutôt que d’avis.

Si nous voulons être des puristes POSIX, nous définissons une ligne comme suit:

Une séquence de zéro ou plusieurs caractères non <nouvelle ligne> plus un caractère <nouvelle ligne> de fin.

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

Une ligne incomplète en tant que:

Une séquence d'un ou plusieurs caractères autres que <nouvelle ligne> à la fin du fichier.

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

Un fichier texte en tant que:

Un fichier qui contient des caractères organisés en zéro ligne ou plus. Les lignes ne contiennent pas de caractères NUL et aucune ne peut dépasser {LINE_MAX} octets, y compris le caractère <nouvelle ligne>. Bien que POSIX.1-2008 ne fasse pas la distinction entre les fichiers texte et les fichiers binaires (voir la norme ISO C), de nombreux utilitaires ne produisent que des résultats prévisibles ou significatifs lorsqu'ils fonctionnent sur des fichiers texte. Les utilitaires standard qui ont de telles restrictions spécifient toujours des "fichiers texte" dans leurs sections STDIN ou INPUT FILES.

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

Une chaîne en tant que:

Une séquence d'octets contigus terminée par et incluant le premier octet nul.

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

Nous pouvons donc en déduire que le seul moment où nous rencontrerons potentiellement ​​tout type de problème surviendra si nous traitons le concept de ligne d'un fichier ou d'un fichier en tant que un fichier texte (étant un fichier texte est une organisation de zéro ligne ou plus, et une ligne que nous connaissons doit se terminer par un <newline>).

Exemple: wc -l filename.

Le manuel de wcname __ indique:

Une ligne est définie comme une chaîne de caractères délimitée par un caractère <nouvelle ligne>.

Quelles sont les implications pour les fichiers JavaScript, HTML et CSS, étant donné qu’il s’agit de fichiers text?

Dans les navigateurs, les IDE modernes et les autres applications frontales, le fait de sauter EOL chez EOF ne pose aucun problème. Les applications analyseront les fichiers correctement. Comme tous les systèmes d’exploitation ne sont pas conformes à la norme POSIX, il serait donc peu pratique pour les outils non-OS (par exemple, les navigateurs) de traiter les fichiers conformément à la norme POSIX (ou à toute norme de niveau OS).

Par conséquent, nous pouvons être relativement confiants sur le fait que EOL à EOF n'aura quasiment aucun impact négatif au niveau de l'application, qu'il fonctionne sous UNIX ou non.

À ce stade, nous pouvons affirmer avec certitude que le fait de sauter EOL en EOF est sans danger lorsqu'il est question de JS, HTML, CSS au niveau du client. En fait, nous pouvons affirmer que réduire au minimum l'un de ces fichiers, sans <newline>, est sans danger.

Nous pouvons aller plus loin et dire qu'en ce qui concerne NodeJS, il ne peut pas non plus adhérer à la norme POSIX, étant donné qu'il peut fonctionner dans des environnements non compatibles POSIX.

Que nous reste-t-il alors? Outillage de niveau système.

Cela signifie que les seuls problèmes pouvant survenir concernent les outils qui s'efforcent d'adhérer leurs fonctionnalités à la sémantique de POSIX (par exemple, la définition d'une ligne, comme indiqué dans wcname__).

Même dans ce cas, tous les shells n'adhérent pas automatiquement à POSIX. Bash par exemple ne prend pas par défaut le comportement POSIX. Il existe un commutateur pour l'activer: POSIXLY_CORRECT.

Matière à réflexion sur la valeur de la fin de vie en tant que <nouvelle ligne>: https://www.rfc-editor.org/old/EOLstory.txt

Pour rester sur la voie de l'outillage, à toutes fins pratiques, considérons ceci:

Travaillons avec un fichier sans EOL. Au moment de cette écriture, le fichier dans cet exemple est un JavaScript minifié sans 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

Notez que la taille du fichier catcorrespond exactement à la somme de ses parties individuelles. Si la concaténation de fichiers JavaScript concerne les fichiers JS, la meilleure solution consiste à démarrer chaque fichier JavaScript avec un point-virgule.

Comme quelqu'un d'autre l'a mentionné dans ce fil de discussion: que se passe-t-il si vous voulez catdeux fichiers dont la sortie ne devient qu'une ligne au lieu de deux? En d'autres termes, catfait ce qu'il est censé faire.

manof catne mentionne que les entrées de lecture jusqu’à EOF, pas <nouvelle ligne>. Notez que le commutateur -n de cataffichera également une ligne terminée par <nouvelle ligne> (ou ligne incomplète) sous forme de ligne - le compte commençant à - 1 (selon le manname__.)

-n Numéroter les lignes de sortie, en commençant à 1.

Maintenant que nous comprenons comment POSIX définit une ligne, ce comportement devient ambigu ou, en réalité, non conforme.

Comprendre le but et la conformité d'un outil donné aidera à déterminer à quel point il est essentiel de terminer les fichiers avec un fichier EOL. En C, C++, Java (JAR), etc ... certaines normes dicteront une nouvelle ligne pour la validité - aucune norme de ce type n'existe pour JS, HTML, CSS.

Par exemple, au lieu d'utiliser wc -l filename, vous pouvez utiliser awk '{x++}END{ print x}' filename et soyez assuré que le succès de la tâche n'est pas compromis par un fichier que nous pouvons traiter et que nous n'avons pas écrit (par exemple, une bibliothèque tierce telle que le JS minifié que nous curlname__d) - sauf si notre intention était vraiment de compter lines au sens de la conformité à POSIX.

Conclusion

Il y aura très peu de cas d'utilisation réels où ignorer EOL en EOF pour certains fichiers texte tels que JS, HTML et CSS aura un impact négatif - voire pas du tout. Si nous comptons sur la présence de <newline>, nous limitons la fiabilité de nos outils aux fichiers que nous créons et nous nous ouvrons aux erreurs potentielles introduites par des fichiers tiers.

Morale de l'histoire: un outillage d'ingénieur qui n'a pas la faiblesse de s'appuyer sur EOL à EOF.

N'hésitez pas à poster des cas d'utilisation tels qu'ils s'appliquent à JS, HTML et CSS où nous pouvons examiner comment le fait de sauter la fin de vie a un effet néfaste.

101
Milan Adamovsky

Cela peut être lié à la différence entre :

  • fichier texte (chaque ligne est supposée se terminer par une fin de ligne)
  • fichier binaire (il n'y a pas de vraies "lignes" à proprement parler, et la longueur du fichier doit être préservée)

Si chaque ligne se termine par une fin de ligne, cela évite, par exemple, que la concaténation de deux fichiers texte fasse en sorte que la dernière ligne de la première passe dans la première ligne de la seconde.

De plus, un éditeur peut vérifier au chargement si le fichier se termine par une fin de ligne, l'enregistrer dans son option locale 'eol' et l'utiliser lors de l'écriture du fichier.

Il y a quelques années (2005), de nombreux éditeurs (ZDE, Eclipse, Scite, ...) ont "oublié" cette EOL finale, qui n'était pas très appréciée .
Non seulement cela, mais ils ont interprété cette EOL finale de manière incorrecte, comme "commencer une nouvelle ligne", et commencent en fait à afficher une autre ligne comme si elle existait déjà.
Cela était très visible avec un fichier texte "approprié" avec un éditeur de texte bien conçu comme vim, par rapport à son ouverture dans l’un des éditeurs ci-dessus. Il affiche une ligne supplémentaire en dessous de la dernière ligne réelle du fichier. Vous voyez quelque chose comme ça:

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

Certains outils s’y attendent. Par exemple, wc s'attend à ceci:

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

Fondamentalement, il existe de nombreux programmes qui ne traiteront pas les fichiers correctement s'ils ne reçoivent pas le fichier final EOL EOF.

GCC vous en avertit parce que cela est prévu dans le standard C. (section 5.1.1.2 apparemment)

Avertissement du compilateur "Pas de nouvelle ligne à la fin du fichier"

19
cgp

Un cas d'utilisation distinct: lorsque votre fichier texte est sous contrôle de version (dans ce cas, spécifiquement sous git bien que cela s'applique aussi aux autres). Si du contenu est ajouté à la fin du fichier, la ligne qui était auparavant la dernière ligne aura été modifiée pour inclure un caractère de nouvelle ligne. Cela signifie que blameing du fichier pour savoir quand cette ligne a été modifiée pour la dernière fois montrera l'ajout de texte, pas le commit avant celui que vous souhaitiez réellement voir.

12
Robin Whittleton

Cela remonte aux tout premiers jours de l’utilisation de simples terminaux. Le caractère de nouvelle ligne a été utilisé pour déclencher un "vidage" des données transférées.

Aujourd'hui, le caractère newline n'est plus nécessaire. Bien sûr, de nombreuses applications ont encore des problèmes si la nouvelle ligne n’existe pas, mais je considérerais cela comme un bogue dans ces applications.

Si toutefois vous avez un format de fichier texte dans lequel vous requis la nouvelle ligne, vous obtenez une vérification de données simple et très économique: si le fichier se termine par une ligne sans nouvelle ligne à la fin, vous savez qu'il est cassé. . Avec un seul octet supplémentaire par ligne, vous pouvez détecter les fichiers cassés avec une grande précision et presque pas de temps processeur.

12
Stefan

En plus des raisons pratiques ci-dessus, je ne serais pas surpris si les concepteurs d'Unix (Thompson, Ritchie et autres) ou de leurs prédécesseurs de Multics réalisaient qu'il y avait une raison théorique à utiliser des terminateurs de ligne plutôt que des séparateurs de ligne: terminateurs, vous pouvez encoder tous les fichiers de lignes possibles. Avec les séparateurs de ligne, il n'y a pas de différence entre un fichier de zéro ligne et un fichier contenant une seule ligne vide; les deux sont codés comme un fichier ne contenant aucun caractère.

Donc, les raisons sont:

  1. Parce que c'est comme ça que POSIX le définit.
  2. Parce que certains outils l'attendent ou "se conduisent mal" sans elle. Par exemple, wc -l ne comptera pas une "ligne" finale si elle ne se termine pas par une nouvelle ligne.
  3. Parce que c'est simple et pratique. Sous Unix, catfonctionne et fonctionne sans complication. Il ne fait que copier les octets de chaque fichier, sans aucune interprétation. Je ne pense pas qu'il existe un équivalent DOS à catname__. Utiliser copy a+b c finira par fusionner la dernière ligne du fichier aavec la première ligne du fichier bname__.
  4. Parce qu'un fichier (ou un flux) de zéro ligne peut être distingué d'un fichier d'une ligne vide.
11
John Wiersba

Il existe également un problème de programmation pratique avec des fichiers manquant de nouvelles lignes à la fin: le readBash intégré (je ne connais pas les autres implémentations de readname__) ne fonctionne pas comme prévu:

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

Ceci affiche niquement fooname __! La raison en est que lorsque readrencontre la dernière ligne, il écrit le contenu dans $line mais renvoie le code de sortie 1 car il a atteint EOF. Cela rompt la boucle whilename__, nous n'atteignons donc jamais la partie echo $line. Si vous voulez gérer cette situation, vous devez procéder comme suit:

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

C'est-à-dire, faites echosi reada échoué à cause d'une ligne non vide à la fin du fichier. Naturellement, dans ce cas, il y aura une nouvelle ligne supplémentaire dans la sortie qui ne figurait pas dans l'entrée.

10
l0b0

On peut supposer simplement que certains codes d'analyse s'attendaient à ce qu'il soit là.

Je ne suis pas sûr que je considérerais cela comme une "règle" et ce n’est certainement pas une chose à laquelle j’adhère religieusement. La plupart des codes sensibles sauront analyser le texte (y compris les codages) ligne par ligne (tout choix de fin de ligne), avec ou sans nouvelle ligne sur la dernière ligne.

En effet, si vous terminez par une nouvelle ligne: existe-t-il (en théorie) une dernière ligne vide entre EOL et EOF? Un à méditer ...

9
Marc Gravell

Je me suis demandé cela pendant des années. Mais je suis tombé sur une bonne raison aujourd'hui.

Imaginez un fichier avec un enregistrement sur chaque ligne (ex: un fichier CSV). Et que l'ordinateur enregistrait des enregistrements à la fin du fichier. Mais il s'est soudainement écrasé. Gee était la dernière ligne complète? (pas une belle situation)

Mais si nous terminons toujours la dernière ligne, nous le saurons (il suffit de vérifier si la dernière ligne est terminée). Sinon, nous devrions probablement jeter la dernière ligne à chaque fois, juste pour être en sécurité.

7
symbiont

Pourquoi les fichiers (texte) doivent-ils se terminer par une nouvelle ligne?

Aussi bien exprimé par beaucoup, parce que:

  1. De nombreux programmes ne se comportent pas bien ou échouent sans cela.

  2. Même les programmes qui gèrent bien un fichier n'ont pas de fin '\n', la fonctionnalité de l'outil peut ne pas répondre aux attentes de l'utilisateur - ce qui peut être flou dans ce cas.

  3. Les programmes refusent rarement final '\n' (je n'en connais aucun).


Pourtant, cela soulève la question suivante:

Que doit faire le code à propos des fichiers texte sans nouvelle ligne?

  1. Le plus important - Ne pas écrire de code qui suppose qu'un fichier texte se termine par une nouvelle ligne. En supposant un fichier conforme à un format entraîne la corruption des données, des attaques de pirates informatiques et des plantages. Exemple:

    // 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. Si le '\n' final est nécessaire, avertissez l’utilisateur de son absence et des mesures prises. IOWs, validez le format du fichier. Remarque: Ceci peut inclure une limite à la longueur de ligne maximale, au codage de caractères, etc.

  3. Définissez clairement, documentez, le traitement par le code d'un '\n' final manquant.

  4. Ne pas, autant que possible, générer un fichier manquant de la fin '\n'.

7
chux

Il est très tard, mais un problème de traitement de fichier s’est posé, car les fichiers ne se terminaient pas par un saut de ligne vide. Nous étions en train de traiter des fichiers texte avec sed et sed en omettant la dernière ligne de la sortie, ce qui provoquait une structure json non valide et l'envoi du reste du processus à l'état d'échec.

Tout ce que nous faisions était:

Il y a un exemple de fichier dit: foo.txt avec un contenu json à l'intérieur.

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

Le fichier a été créé dans une machine veuve et les scripts de fenêtre le traitaient à l’aide de commandes powershall. Tout bon.

Lorsque nous avons traité le même fichier à l'aide de sed command sed 's|value|newValue|g' foo.txt > foo.txt.tmp Le fichier nouvellement généré a été

[{
    someProp: value
},
{
    someProp: value

et boum, il a échoué le reste des processus à cause du JSON invalide.

C'est donc toujours une bonne pratique de terminer votre fichier avec une nouvelle ligne vide.

3
Arpit

J'avais toujours l'impression que la règle venait de l'époque où analyser un fichier sans fin de ligne était difficile. C'est-à-dire que vous finiriez par écrire du code où une fin de ligne a été définie par le caractère EOL ou EOF. Il était simplement plus simple de supposer une ligne terminée par EOL.

Cependant, je crois que la règle est dérivée des compilateurs C nécessitant la nouvelle ligne. Et comme indiqué sur avertissement du compilateur "Pas de nouvelle ligne à la fin du fichier" , #include n’ajoutera pas de nouvelle ligne.

3
he_the_great

Imaginez que le fichier est en cours de traitement alors qu'il est encore généré par un autre processus.

Cela pourrait avoir à voir avec ça? Un indicateur qui indique que le fichier est prêt à être traité.

0
Pippen_001