La norme C stipule que les fichiers texte doivent se terminer par un saut de ligne, sinon les données situées après le dernier saut de ligne risquent de ne pas être lues correctement.
ISO/IEC 9899:2011 §7.21.2 Streams (flux)
Un flux de texte est une séquence ordonnée de caractères composée en lignes, chaque ligne est composée de zéro ou plusieurs caractères plus un caractère de fin de ligne. Que la dernière ligne nécessite un caractère de fin de ligne est définie par l'implémentation. Les caractères peuvent être ajoutés, modifiés ou supprimés en entrée et en sortie afin de se conformer à différentes conventions de représentation du texte dans l'environnement hôte. Ainsi, il n'est pas nécessaire qu'il y ait une correspondance un à entre les caractères d'un flux et ceux de la représentation externe. représentation externe. Les données lues à partir d'un flux de texte seront nécessairement comparées aux données qui ont été précédemment écrites dans ce flux. qui ont été précédemment écrites dans ce flux uniquement si : les données ne sont constituées que de caractères d'impression et des caractères de contrôle (tabulation horizontale et tabulation verticale). caractères d'impression et les caractères de contrôle tabulation horizontale et nouvelle ligne ; aucun caractère de nouvelle ligne n'est immédiatement précédé de caractères d'espacement ; et le dernier caractère est un caractère de nouvelle ligne. Si les caractères d'espacement qui sont écrits immédiatement avant un caractère de nouvelle ligne apparaissent lors de la lecture, cela dépend de l'implémentation.
Je ne m'attendais pas à ce qu'un saut de ligne manquant à la fin d'un fichier puisse causer des problèmes dans le système de gestion de l'information. bash
(ou tout autre shell Unix), mais cela semble être le problème de manière reproductible ( $
est l'invite dans cette sortie) :
$ 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 # Preferred notation in bash
abc
def
ghi
xxx
$ for line in $(cat y); do echo $line; done # UUOC Award pending
abc
def
ghi
xxx
$
Il ne se limite pas non plus à bash
- Le shell Korn ( ksh
) y zsh
se comportent aussi comme ça. Je vis, j'apprends ; merci d'avoir soulevé la question.
Comme le montre le code ci-dessus, l'élément cat
lit le fichier entier. Le site for line in `cat $DATAFILE`
collecte toutes les sorties et remplace les séquences arbitraires d'espaces blancs par un seul blanc (je conclus que chaque ligne du fichier ne contient aucun blanc).
Testé sur Mac OS X 10.7.5.
Que dit POSIX ?
Le système POSIX read
La spécification de la commande dit :
L'utilitaire read doit lire une seule ligne à partir de l'entrée standard.
Par défaut, à moins que l'option -r
est spécifiée, <backslash> doit agir comme un caractère d'échappement. Un <backslash> non encapsulé conserve la valeur littérale du caractère suivant, à l'exception d'un <newline>. Si une <nouvelle ligne> suit le <backslash>, l'utilitaire de lecture doit l'interpréter comme une continuation de ligne. Les caractères <backslash> et <newline>
doivent être supprimés avant de diviser l'entrée en champs. Tous les autres caractères <backslash> non encodés doivent être supprimés après la division de l'entrée en champs.
Si l'entrée standard est un périphérique terminal et que le shell invoquant est interactif, read demandera une ligne de continuation lorsqu'il lira une ligne d'entrée se terminant par un <backslash> <newline>, à moins que l'option -r
est spécifiée.
La terminaison <nouvelle ligne> (le cas échéant) sont supprimés de l'entrée et les résultats sont fractionnés en champs comme dans le shell pour les résultats de l'expansion des paramètres (voir Fractionnement des champs) ; [...]
Notez que "(s'il y en a)" (accentuation ajoutée dans la citation) ! Il me semble que s'il n'y a pas de nouvelle ligne, le résultat devrait quand même être lu. D'un autre côté, il est également dit :
STDIN
L'entrée standard doit être un fichier texte.
et on en revient au débat sur la question de savoir si un fichier qui ne se termine pas par une nouvelle ligne est un fichier texte ou non.
Cependant, le raisonnement sur la même page documente :
Bien que l'entrée standard doive être un fichier texte, et donc se terminera toujours par une <nouvelle ligne> (sauf s'il s'agit d'un fichier vide), le traitement des lignes de continuation lorsque la commande -r
n'est pas utilisée peut avoir pour conséquence que l'entrée ne se termine pas par une <nouvelle ligne>. Cela se produit si la dernière ligne du fichier d'entrée se termine par une <backslash> <newline>. C'est pour cette raison que "s'il y en a" est utilisé dans "La <nouvelle> de fin (s'il y en a) doit être supprimée de l'entrée" dans la description. Il ne s'agit pas d'un assouplissement de l'exigence selon laquelle l'entrée standard doit être un fichier texte.
Ce raisonnement doit signifier que le fichier texte est censé se terminer par une nouvelle ligne.
La définition POSIX d'un fichier texte est la suivante :
3.395 Fichier texte
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 <nouvelle ligne>. Bien que la norme 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 opèrent sur des fichiers texte. Les utilitaires standard qui ont de telles restrictions spécifient toujours "fichiers texte" dans leurs sections STDIN ou INPUT FILES.
Cela ne stipule pas directement "se termine par une <nouvelle ligne>", mais s'en remet à la norme C et dit "Un fichier qui contient des caractères organisés en zéro ou plus". lignes "et quand nous regardons la définition POSIX d'une "Ligne", elle dit :
3.206 Ligne
Une séquence de zéro ou plus caractères non <newline> plus un caractère caractère de terminaison <nouvelle ligne>.
Ainsi, selon la définition POSIX, un fichier doit se terminer par une nouvelle ligne de fin, car il est composé de lignes et chaque ligne doit se terminer par une nouvelle ligne de fin.
Une solution au problème de l'absence de nouvelle ligne terminale
Note Gordon Davisson 's réponse . Un simple test montre que son observation est exacte :
$ while read line; do echo $line; done < y; echo $line
abc
def
ghi
xxx
$
Par conséquent, sa technique de :
while read line || [ -n "$line" ]; do echo $line; done < y
ou :
cat y | while read line || [ -n "$line" ]; do echo $line; done
fonctionnera pour les fichiers sans nouvelle ligne à la fin (au moins sur ma machine).
Je suis toujours surpris de constater que les shells laissent tomber le dernier segment (on ne peut pas l'appeler une ligne car il ne se termine pas par une nouvelle ligne) de l'entrée, mais il pourrait y avoir une justification suffisante dans POSIX pour le faire. Et il est clair qu'il est préférable de s'assurer que vos fichiers texte sont vraiment des fichiers texte se terminant par une nouvelle ligne.