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?
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?
Réponses:
Parce que c'est ainsi que la norme POSIX définit une ligne :
- 3.206 Ligne
- Une séquence de zéro ou plusieurs caractères non <nouvelle> plus un caractère <nouvelle> 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 terminé.
Il y a au moins un avantage important à cette directive lorsque vous travaillez sur un émulateur de terminal: tous les outils Unix attendent cette convention et fonctionnent avec elle. Par exemple, lors de la concaténation de fichiers avec cat
, un fichier terminé par une nouvelle ligne aura un effet différent de celui sans:
$ more a.txt
foo
$ more b.txt
bar$ more c.txt
baz
$ cat {a,b,c}.txt
foo
barbaz
Et, comme l'exemple précédent le montre également, lors de l'affichage du fichier sur la ligne de commande (par exemple via more
), un fichier terminé par une nouvelle ligne se traduit par un affichage correct. Un fichier incorrectement 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înera un travail supplémentaire lors de l'utilisation des outils Unix par défaut.
Pensez-y différemment: si les lignes ne sont pas terminées par un saut de ligne, rendre les commandes aussi cat
utiles est beaucoup plus difficile: comment créer une commande pour concaténer des fichiers de telle sorte que
b.txt
et c.txt
?Bien sûr, cela peut être résolu, mais vous devez rendre l'utilisation de cat
plus complexe (en ajoutant des arguments de ligne de commande positionnels, par exemple cat a.txt --no-newline b.txt c.txt
), et maintenant la commande plutôt que chaque fichier individuel contrôle la façon 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 qui est censée être poursuivie plutôt que terminée. Eh bien, maintenant vous êtes coincé avec la même situation que sur POSIX, sauf inversé (continuation de ligne plutôt que caractère de fin de ligne).
Maintenant, sur les systèmes non conformes à POSIX (de nos jours, c'est principalement Windows), le point est théorique: 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 «du texte séparé par des nouvelles lignes» (notez l'emphase). C'est tout à fait valable. Cependant, pour les données structurées (par exemple, le code de programmation), cela rend l'analyse plus compliqué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 pourrait être plus facile de modifier le flux de jetons plutôt que l'analyseur - en d'autres termes, ajoutez un jeton de "nouvelle ligne artificielle" à la fin de l'entrée.
cat
d'une manière à la fois utile et cohérente.
Chaque ligne doit se terminer par un caractère de nouvelle ligne, y compris la dernière. Certains programmes ont des problèmes de traitement de la dernière ligne d'un fichier s'il n'est pas terminé.
GCC l'avertit non pas parce qu'il ne peut pas traiter le fichier, mais parce qu'il doit le faire dans le cadre de la norme.
La norme du langage C indique qu'un fichier source qui n'est pas vide doit se terminer par un caractère de nouvelle ligne, qui ne doit pas être immédiatement précédé d'une barre oblique inverse.
Puisqu'il s'agit d'une clause "doit", nous devons émettre un message de diagnostic pour une violation de cette règle.
C'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 la norme ISO C 1990).
Référence: Les archives de messagerie GCC / GNU .
wc -l
ne comptera pas la dernière ligne d'un fichier s'il n'est pas terminé. En outre, cat
joignera la dernière ligne d'un fichier avec la première ligne du fichier suivant en une seule si la dernière ligne du premier fichier n'est pas terminée. À peu près n'importe quel programme qui recherche des nouvelles lignes comme délimiteur a le potentiel de gâcher cela.
wc
a déjà été mentionné ....
cat
et wc
)?
Cette réponse est une tentative de réponse technique plutôt que d'opinion.
Si nous voulons être des puristes POSIX, nous définissons une ligne comme:
Une séquence de zéro ou plusieurs caractères non <nouvelle> plus un caractère <nouvelle> de fin.
Source: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206
Une ligne incomplète comme:
Une séquence d'un ou plusieurs caractères non <nouvelle> à la fin du fichier.
Source: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_195
Un fichier texte comme:
Un fichier qui contient des caractères organisés en zéro ou plusieurs lignes. Les lignes ne contiennent pas de caractères NUL et aucune ne peut dépasser {LINE_MAX} octets de longueur, y compris le caractère <newline>. Bien que POSIX.1-2008 ne fasse pas de distinction entre les fichiers texte et les fichiers binaires (voir la norme ISO C), de nombreux utilitaires ne produisent une sortie prévisible ou significative que 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 comme:
Une séquence contiguë d'octets terminée par et incluant le premier octet nul.
Source: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_396
De cela, nous pouvons déduire que la seule fois où nous pourrons potentiellement rencontrer un type de problème, c'est si nous traitons le concept d'une ligne de fichier ou d'un fichier comme un fichier texte (étant donné qu'un fichier texte est une organisation de zéro ou plusieurs lignes, et une ligne que nous connaissons doit se terminer par un <newline>).
Exemple: wc -l filename
.
Dans le wc
manuel, nous lisons:
Une ligne est définie comme une chaîne de caractères délimitée par un caractère <newline>.
Quelles sont les implications pour les fichiers JavaScript, HTML et CSS étant alors que ce sont des fichiers texte ?
Dans les navigateurs, les IDE modernes et les autres applications frontales, il n'y a aucun problème à ignorer EOL à EOF. Les applications analysent correctement les fichiers. Il doit, puisque tous les systèmes d'exploitation ne sont pas conformes à la norme POSIX, il serait donc impossible pour les outils non OS (par exemple les navigateurs) de gérer les fichiers conformément à la norme POSIX (ou à toute norme de niveau OS).
Par conséquent, nous pouvons être relativement confiants qu'EOL chez EOF n'aura pratiquement aucun impact négatif au niveau de l'application, peu importe s'il s'exécute sur un système d'exploitation UNIX.
À ce stade, nous pouvons affirmer avec certitude que sauter EOL à EOF est sûr lorsque vous traitez avec JS, HTML, CSS côté client. En fait, nous pouvons affirmer que la réduction de l'un de ces fichiers, ne contenant pas de <newline>, est sûre.
Nous pouvons aller un peu plus loin et dire qu'en ce qui concerne NodeJS, il ne peut pas non plus adhérer au standard POSIX étant donné qu'il peut fonctionner dans des environnements non conformes à POSIX.
Que nous reste-t-il alors? Outillage au niveau du système.
Cela signifie que les seuls problèmes qui peuvent 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 wc
).
Même ainsi, tous les shells n'adhéreront pas automatiquement à POSIX. Par exemple, Bash n'a pas par défaut le comportement POSIX. Il y a un interrupteur pour lui permettre: POSIXLY_CORRECT
.
Matière à réflexion sur la valeur de l'EOL étant <newline>: https://www.rfc-editor.org/old/EOLstory.txt
En restant sur la piste de l'outillage, à toutes fins pratiques, considérons ceci:
Travaillons avec un fichier qui n'a pas de fin de vie. À ce jour, 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 cat
taille du fichier est exactement la somme de ses parties individuelles. Si la concaténation des fichiers JavaScript est une préoccupation pour les fichiers JS, la préoccupation la plus appropriée serait de démarrer chaque fichier JavaScript avec un point-virgule.
Comme quelqu'un d'autre l'a mentionné dans ce fil: que faire si vous voulez cat
deux fichiers dont la sortie ne devient qu'une ligne au lieu de deux? En d'autres termes, cat
fait ce qu'il est censé faire.
Le man
de cat
ne mentionne que la lecture des entrées jusqu'à EOF, pas <newline>. Notez que le -n
basculement de cat
imprimera également une ligne non terminée (nouvelle ligne) (ou ligne incomplète ) en tant que ligne - étant donné que le décompte commence à 1 (selon le man
.)
-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 vraiment non conforme.
La compréhension de l'objectif et de la conformité d'un outil donné aidera à déterminer à quel point il est essentiel de terminer les fichiers avec une fin de vie. En C, C ++, Java (JAR), etc ... certaines normes imposent une nouvelle ligne pour la validité - aucune norme de ce type n'existe pour JS, HTML, CSS.
Par exemple, au lieu d'en utiliser wc -l filename
un, vous pouvez le faire 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 pourrions vouloir traiter que nous n'avons pas écrit (par exemple une bibliothèque tierce telle que le JS minifié que nous avons curl
) - à moins que notre l'intention était vraiment de compter les lignes au sens conforme POSIX.
Conclusion
Il y aura très peu de cas d'utilisation réels où sauter EOL à 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 uniquement aux fichiers que nous créons et nous nous exposons aux erreurs potentielles introduites par des fichiers tiers.
Morale de l'histoire: des outils d'ingénieur qui n'ont pas la faiblesse de s'appuyer sur EOL chez EOF.
N'hésitez pas à publier des cas d'utilisation tels qu'ils s'appliquent à JS, HTML et CSS où nous pouvons examiner comment le fait de sauter EOL a un effet négatif.
Cela peut être lié à la différence entre :
Si chaque ligne se termine par une fin de ligne, cela évite, par exemple, que la concaténation de deux fichiers texte transforme la dernière ligne de la première ligne en première ligne de la seconde.
De plus, un éditeur peut vérifier à la charge si le fichier se termine en fin de ligne, l'enregistre dans son option locale «eol» et l'utilise 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 mal interprété cette fin de vie finale, comme «commencer une nouvelle ligne», et ont commencé à 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 comporté comme vim, par rapport à l'ouvrir dans l'un des éditeurs ci-dessus. Il a affiché 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
Certains outils s'y attendent. Par exemple, wc
attend cela:
$ echo -n "Line not ending in a new line" | wc -l
0
$ echo "Line ending with a new line" | wc -l
1
wc
cela ne s’attend pas à cela, dans la mesure où cela fonctionne simplement dans la définition POSIX d’une "ligne" par opposition à la compréhension intuitive de la "ligne" par la plupart des gens.
wc -l
d'imprimer 1
dans les deux cas, mais certaines personnes pourraient dire que le deuxième cas devrait s'imprimer 2
.
\n
à un terminateur de ligne, plutôt qu'à un séparateur de ligne, comme le fait POSIX / UNIX, alors attendre le deuxième cas pour imprimer 2 est absolument fou.
Fondamentalement, il existe de nombreux programmes qui ne traiteront pas les fichiers correctement s'ils n'obtiennent pas le EOL EOF final.
GCC vous en avertit car il fait partie de la norme C. (section 5.1.1.2 apparemment)
Avertissement du compilateur "Pas de nouvelle ligne à la fin du fichier"
Cela remonte aux tout premiers jours où de simples terminaux étaient utilisés. 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 de nouvelle ligne n'est plus requis. Bien sûr, de nombreuses applications ont toujours des problèmes si la nouvelle ligne n'est pas là, mais je considérerais qu'il s'agit d'un bogue dans ces applications.
Si toutefois vous avez un format de fichier texte où vous avez besoin de la nouvelle ligne, vous obtenez une vérification des données simple très bon marché: si le fichier se termine par une ligne qui n'a pas de nouvelle ligne à la fin, vous savez que le fichier est cassé. Avec un seul octet supplémentaire pour chaque ligne, vous pouvez détecter les fichiers cassés avec une grande précision et presque pas de temps CPU.
Un cas d'utilisation distinct: lorsque votre fichier texte est contrôlé par version (dans ce cas spécifiquement sous git bien qu'il s'applique également 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 blame
le fait de savoir quand le fichier a été modifié pour la dernière fois affichera l'ajout de texte, et non le commit avant que vous vouliez réellement voir.
\n
). Problème résolu.
En plus des raisons pratiques ci-dessus, cela ne me surprendrait pas si les créateurs d'Unix (Thompson, Ritchie et al.) Ou leurs prédécesseurs Multics se rendaient compte qu'il y avait une raison théorique d'utiliser des terminateurs de ligne plutôt que des séparateurs de ligne: Avec la ligne terminateurs, vous pouvez encoder tous les fichiers de lignes possibles. Avec les séparateurs de lignes, il n'y a pas de différence entre un fichier de zéro lignes et un fichier contenant une seule ligne vide; les deux sont codés comme un fichier contenant zéro caractère.
Donc, les raisons sont:
wc -l
ne comptera pas une "ligne" finale si elle ne se termine pas par une nouvelle ligne.cat
ça marche et ça marche sans complication. Il copie simplement les octets de chaque fichier, sans aucun besoin d'interprétation. Je ne pense pas qu'il existe un équivalent DOS cat
. L'utilisation copy a+b c
finira par fusionner la dernière ligne de fichier a
avec la première ligne de fichier b
.Je me le demande depuis 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 écrivait des enregistrements à la fin du fichier. Mais il s'est soudainement écrasé. Gee était la dernière ligne terminée? (pas une bonne situation)
Mais si nous terminons toujours la dernière ligne, nous le saurons (vérifiez simplement si la dernière ligne est terminée). Sinon, nous devrons probablement rejeter la dernière ligne à chaque fois, juste pour être sûr.
Vraisemblablement simplement que certains codes d'analyse s'attendaient à ce qu'il soit là.
Je ne suis pas sûr de le considérer comme une "règle", et ce n'est certainement pas quelque chose auquel j'adhère religieusement. Le code le plus sensé saura comment analyser le texte (y compris les encodages) ligne par ligne (tout choix de fin de ligne), avec ou sans retour à la ligne sur la dernière ligne.
En effet - si vous terminez avec une nouvelle ligne: y a-t-il (en théorie) une ligne finale vide entre l'EOL et l'EOF? À méditer ...
Il y a aussi un problème de programmation pratique avec des fichiers manquant de nouvelles lignes à la fin: le read
Bash intégré (je ne connais pas les autres read
implémentations) ne fonctionne pas comme prévu:
printf $'foo\nbar' | while read line
do
echo $line
done
Cela imprime seulementfoo
! La raison en est que lorsqu'il read
rencontre la dernière ligne, il écrit le contenu $line
mais renvoie le code de sortie 1 car il a atteint EOF. Cela rompt la while
boucle, donc nous n'atteignons jamais la echo $line
pièce. Si vous souhaitez gérer cette situation, vous devez procéder comme suit:
while read line || [ -n "${line-}" ]
do
echo $line
done < <(printf $'foo\nbar')
Autrement dit, faites le echo
si l' read
échec en raison 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 n'était pas dans l'entrée.
Pourquoi les fichiers (texte) devraient-ils se terminer par une nouvelle ligne?
Comme beaucoup l'ont exprimé, car:
De nombreux programmes ne se comportent pas bien ou échouent sans cela.
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 ne pas être clair dans ce cas d'angle.
Les programmes interdisent rarement la finale '\n'
(je n'en connais pas).
Pourtant, cela soulève la question suivante:
Que doit faire le code sur les fichiers texte sans nouvelle ligne?
Plus important - N'écrivez pas de code qui suppose qu'un fichier texte se termine par une nouvelle ligne . Supposer qu'un fichier est conforme à un format entraîne une corruption des données, des attaques de pirates 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
...
}
Si la fin finale '\n'
est nécessaire, avertissez l'utilisateur de son absence et des mesures prises. IOWs, validez le format du fichier. Remarque: cela peut inclure une limite à la longueur de ligne maximale, le codage des caractères, etc.
Définissez clairement, documentez, la gestion par le code d'une finale manquante '\n'
.
Ne générez pas, autant que possible, un fichier sans la fin '\n'
.
Il est très tard ici, mais je viens de rencontrer un bogue dans le traitement des fichiers et cela vient du fait que les fichiers ne se terminent pas par une nouvelle ligne vide. Nous traitions des fichiers texte avec sed
et sed
omettions la dernière ligne de la sortie, ce qui provoquait l'échec de la structure json et l'envoi du reste du processus.
Tout ce que nous faisions était:
Il y a un exemple de fichier qui dit: foo.txt
avec du json
contenu à l'intérieur.
[{
someProp: value
},
{
someProp: value
}] <-- No newline here
Le fichier a été créé dans la machine à veuves et les scripts de fenêtre traitaient ce fichier à l'aide des commandes PowerShell. Tout bon.
Lorsque nous avons traité le même fichier à l'aide de la sed
commandesed 's|value|newValue|g' foo.txt > foo.txt.tmp
Le fichier nouvellement généré était
[{
someProp: value
},
{
someProp: value
et boom, il a échoué le reste des processus en raison du JSON non valide.
Il est donc toujours recommandé de terminer votre fichier avec une nouvelle ligne vide.
J'avais toujours l'impression que la règle venait du temps où l'analyse d'un fichier sans retour à la ligne était difficile. Autrement dit, vous finiriez par écrire du code où une fin de ligne était définie par le caractère EOL ou EOF. Il était simplement plus simple de supposer qu'une ligne se terminait 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 l' avertissement du compilateur «Pas de nouvelle ligne à la fin du fichier» , #include n'ajoutera pas de nouvelle ligne.
Imaginez que le fichier est en cours de traitement alors qu'il est toujours 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é.
Personnellement, j'aime les nouvelles lignes à la fin des fichiers de code source.
Il peut avoir son origine avec Linux ou tous les systèmes UNIX d'ailleurs. Je me souviens des erreurs de compilation (gcc si je ne me trompe pas) car les fichiers de code source ne se terminaient pas par une nouvelle ligne vide. Pourquoi a-t-il été fait de cette façon?
À mon humble avis, c'est une question de style personnel et d'opinion.
Autrefois, je ne mettais pas cette nouvelle ligne. Un caractère enregistré signifie plus de vitesse grâce à ce modem 14,4K.
Plus tard, j'ai mis cette nouvelle ligne pour qu'il soit plus facile de sélectionner la ligne finale en utilisant shift + downarrow.