En utilisant des systèmes de contrôle de version, je suis ennuyé par le bruit lorsque le diff dit No newline at end of file
.
Je me demandais donc: comment ajouter une nouvelle ligne à la fin d'un fichier pour se débarrasser de ces messages?
Pour désinfecter récursivement un projet, j'utilise ce oneliner:
git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done
Explication:
git ls-files -z
répertorie les fichiers dans le référentiel. Il prend un modèle facultatif comme paramètre supplémentaire qui peut être utile dans certains cas si vous souhaitez limiter l'opération à certains fichiers/répertoires. Vous pouvez également utiliser find -print0 ...
ou des programmes similaires pour lister les fichiers affectés - assurez-vous juste qu'il émet NUL
- entrées délimitées.
while IFS= read -rd '' f; do ... done
parcourt les entrées, en manipulant en toute sécurité les noms de fichiers comprenant des espaces et/ou des retours à la ligne.
tail -c1 < "$f"
lit le dernier caractère d'un fichier.
read -r _
quitte avec un état de sortie différent de zéro si un saut de ligne de fin est manquant.
|| echo >> "$f"
ajoute une nouvelle ligne au fichier si l'état de sortie de la commande précédente était différent de zéro.
Voilà :
sed -i -e '$a\' file
Et alternativement pour OS X sed
:
sed -i '' -e '$a\' file
Cela ajoute \n
à la fin du fichier uniquement s'il ne se termine pas déjà par une nouvelle ligne. Donc, si vous l'exécutez deux fois, il n'ajoutera pas de nouvelle ligne:
$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0
Regarde:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
donc echo "" >> noeol-file
devrait faire l'affaire. (Ou vouliez-vous demander d'identifier ces fichiers et de les réparer?)
modifier a supprimé le ""
de echo "" >> foo
(voir le commentaire de @ yuyichao) edit2 a ajouté le ""
encore ( mais voir le commentaire de @Keith Thompson)
Une autre solution utilisant ed
. Cette solution n'affecte que la dernière ligne et uniquement si \n
Est manquant:
ed -s file <<< w
Cela fonctionne essentiellement en ouvrant le fichier pour le modifier via un script, le script est la commande unique w
, qui réécrit le fichier sur le disque. Il est basé sur cette phrase trouvée dans la page man ed(1)
:
LIMITATIONS (...) Si un fichier texte (non binaire) n'est pas terminé par un caractère de nouvelle ligne, Puis ed en ajoute un à sa lecture/écriture. Dans le cas d'un fichier binaire , Ed n'ajoute pas de nouvelle ligne en lecture/écriture.
Ajouter une nouvelle ligne indépendamment:
echo >> filename
Voici un moyen de vérifier si une nouvelle ligne existe à la fin avant d'en ajouter une, en utilisant Python:
f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f
Un moyen simple, portable et conforme à POSIX pour ajouter une nouvelle ligne finale absente à un fichier texte serait:
[ -n "$(tail -c1 file)" ] && echo >> file
Cette approche n'a pas besoin de lire l'intégralité du fichier; il peut simplement chercher à EOF et travailler à partir de là.
Cette approche n'a pas non plus besoin de créer de fichiers temporaires derrière votre dos (par exemple sed -i), donc les liens physiques ne sont pas affectés.
echo ajoute une nouvelle ligne au fichier uniquement lorsque le résultat de la substitution de commande est une chaîne non vide. Notez que cela ne peut se produire que si le fichier n'est pas vide et que le dernier octet n'est pas une nouvelle ligne.
Si le dernier octet du fichier est un saut de ligne, tail le renvoie, puis la substitution de commandes le supprime; le résultat est une chaîne vide. Le test -n échoue et l'écho ne s'exécute pas.
Si le fichier est vide, le résultat de la substitution de commande est également une chaîne vide, et à nouveau l'écho ne s'exécute pas. Ceci est souhaitable, car un fichier vide n'est pas un fichier texte non valide, ni équivalent à un fichier texte non vide avec une ligne vide.
La solution la plus rapide est:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
C'est vraiment rapide.
Sur un fichier de taille moyenne seq 99999999 >file
cela prend des millisecondes.
D'autres solutions prennent beaucoup de temps:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file 0.013 sec
vi -ecwq file 2.544 sec
paste file 1<> file 31.943 sec
ed -s file <<< w 1m 4.422 sec
sed -i -e '$a\' file 3m 20.931 sec
Fonctionne en frêne, bash, lksh, mksh, ksh93, attsh et zsh mais pas yash.
Si vous avez besoin d'une solution portable pour yash (et de tous les autres shells listés ci-dessus), cela peut devenir un peu plus complexe:
f=file
if [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then printf '\n' >>"$f"
fi
Le moyen le plus rapide de tester si le dernier octet d'un fichier est une nouvelle ligne est de lire uniquement ce dernier octet. Cela pourrait être fait avec tail -c1 file
. Cependant, la manière simpliste de tester si la valeur d'octet est une nouvelle ligne, en fonction de la suppression habituelle par Shell d'une nouvelle ligne de fin dans une extension de commande échoue (par exemple) dans yash, lorsque le dernier caractère du fichier est un UTF- 8 valeur.
La manière correcte, conforme à POSIX, tous les shells (raisonnables) pour savoir si le dernier octet d'un fichier est une nouvelle ligne consiste à utiliser xxd ou hexdump:
tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'
Ensuite, en comparant la sortie de ci-dessus à 0A
fournira un test robuste.
Il est utile d'éviter d'ajouter une nouvelle ligne à un fichier par ailleurs vide.
Fichier qui ne fournira pas le dernier caractère de 0A
, bien sûr:
f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"
Court et doux. Cela prend très peu de temps car il ne fait que lire le dernier octet (rechercher EOF). Peu importe que le fichier soit volumineux. Ajoutez ensuite seulement un octet si nécessaire.
Aucun fichier temporaire nécessaire ni utilisé. Aucun lien dur n'est affecté.
Si ce test est exécuté deux fois, il pas ajoutera une autre nouvelle ligne.
Il vaut mieux corriger l'éditeur de l'utilisateur qui a modifié le fichier en dernier. Si vous êtes la dernière personne à avoir édité le fichier - quel éditeur utilisez-vous, je suppose que textmate ..?
Pourvu qu'il n'y ait pas de null en entrée:
paste - <>infile >&0
... suffirait de toujours ajouter une nouvelle ligne à la fin d'un fichier si elle n'en avait pas déjà une. Et il suffit de lire le fichier d'entrée en une seule fois pour le faire correctement.
Si vous souhaitez simplement ajouter rapidement une nouvelle ligne lors du traitement d'un pipeline, utilisez ceci:
outputting_program | { cat ; echo ; }
il est également compatible POSIX.
Ensuite, bien sûr, vous pouvez le rediriger vers un fichier.
Bien qu'il ne réponde pas directement à la question, voici un script connexe que j'ai écrit pour détecter les fichiers qui ne se terminent pas par une nouvelle ligne. C'est très rapide.
find . -type f | # sort | # sort file names if you like
/usr/bin/Perl -lne '
open FH, "<", $_ or do { print " error: $_"; next };
$pos = sysseek FH, 0, 2; # seek to EOF
if (!defined $pos) { print " error: $_"; next }
if ($pos == 0) { print " empty: $_"; next }
$pos = sysseek FH, -1, 1; # seek to last char
if (!defined $pos) { print " error: $_"; next }
$cnt = sysread FH, $c, 1;
if (!$cnt) { print " error: $_"; next }
if ($c eq "\n") { print " EOL: $_"; next }
else { print "no EOL: $_"; next }
'
Le script Perl lit une liste de noms de fichiers (éventuellement triés) à partir de stdin et pour chaque fichier, il lit le dernier octet pour déterminer si le fichier se termine par une nouvelle ligne ou non. Il est très rapide car il évite de lire l'intégralité du contenu de chaque fichier. Il affiche une ligne pour chaque fichier lu, préfixée avec "error:" si une sorte d'erreur se produit, "empty:" si le fichier est vide (ne se termine pas par un retour à la ligne!), "EOL:" ("fin de ligne ") si le fichier se termine par une nouvelle ligne et" no EOL: "si le fichier ne se termine pas par une nouvelle ligne.
Remarque: le script ne gère pas les noms de fichiers qui contiennent des retours à la ligne. Si vous utilisez un système GNU ou BSD, vous pouvez gérer tous les noms de fichiers possibles en ajoutant -print0 pour rechercher, -z pour trier et -0 pour Perl, comme ceci:
find . -type f -print0 | sort -z |
/usr/bin/Perl -ln0e '
open FH, "<", $_ or do { print " error: $_"; next };
$pos = sysseek FH, 0, 2; # seek to EOF
if (!defined $pos) { print " error: $_"; next }
if ($pos == 0) { print " empty: $_"; next }
$pos = sysseek FH, -1, 1; # seek to last char
if (!defined $pos) { print " error: $_"; next }
$cnt = sysread FH, $c, 1;
if (!$cnt) { print " error: $_"; next }
if ($c eq "\n") { print " EOL: $_"; next }
else { print "no EOL: $_"; next }
'
Bien sûr, vous devrez toujours trouver un moyen d'encoder les noms de fichiers avec des retours à la ligne dans la sortie (à gauche comme exercice pour le lecteur).
La sortie peut être filtrée, si vous le souhaitez, pour ajouter une nouvelle ligne aux fichiers qui n'en ont pas, le plus simplement avec
echo >> "$filename"
L'absence d'une nouvelle ligne finale peut provoquer des bogues dans les scripts car certaines versions de Shell et d'autres utilitaires ne géreront pas correctement une nouvelle ligne finale manquante lors de la lecture d'un tel fichier.
D'après mon expérience, l'absence d'une nouvelle ligne finale est causée par l'utilisation de divers utilitaires Windows pour modifier des fichiers. Je n'ai jamais vu vim provoquer une nouvelle ligne finale manquante lors de la modification d'un fichier, bien qu'il fasse rapport sur ces fichiers.
Enfin, il existe des scripts beaucoup plus courts (mais plus lents) qui peuvent parcourir leurs entrées de nom de fichier pour imprimer les fichiers qui ne se terminent pas par une nouvelle ligne, tels que:
/usr/bin/Perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...
Les éditeurs vi
/vim
/ex
ajoutent automatiquement <EOL>
à EOF à moins que le fichier ne l'ait déjà.
Essayez donc soit:
vi -ecwq foo.txt
ce qui équivaut à:
ex -cwq foo.txt
Essai:
$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt
Pour corriger plusieurs fichiers, vérifiez: Comment corriger 'Pas de nouvelle ligne à la fin du fichier' pour beaucoup de fichiers? à SO
Pourquoi c'est si important? Pour conserver nos fichiers compatible POSIX .
Si votre fichier se termine par Windows fins de ligne \r\n
et vous êtes sous Linux, vous pouvez utiliser cette commande sed
. Il ajoute seulement \r\n
à la dernière ligne si elle n'est pas déjà là:
sed -i -e '$s/\([^\r]\)$/\1\r\n/'
Explication:
-i replace in place
-e script to run
$ matches last line of a file
s substitute
\([^\r]\)$ search the last character in the line which is not a \r
\1\r\n replace it with itself and add \r\n
Si la dernière ligne contient déjà un \r\n
alors l'expression de recherche ne correspondra pas, donc rien ne se passera.
Pour appliquer la réponse acceptée à tous les fichiers du répertoire actuel (plus les sous-répertoires):
$ find . -type f -exec sed -i -e '$a\' {} \;
Cela fonctionne sous Linux (Ubuntu). Sous OS X, vous devrez probablement utiliser -i ''
(non testé).
Vous pouvez écrire un fix-non-delimited-line
script comme:
#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
(){
sysseek -w end -1 || {
syserror -p "Can't seek before the last byte: "
return 1
}
read -r x || print -u0
} <> $file || ret=$?
done
exit $ret
Contrairement à certaines des solutions présentées ici, il
Vous pouvez l'utiliser par exemple comme:
that-script *.txt
ou:
git ls-files -z | xargs -0 that-script
Ajout à réponse de Patrick Oscity , si vous souhaitez simplement l'appliquer à un répertoire spécifique, vous pouvez également utiliser:
find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Exécutez-le dans le répertoire auquel vous souhaitez ajouter des nouvelles lignes.
echo $'' >> <FILE_NAME>
ajoutera une ligne vierge à la fin du fichier.
echo $'\n\n' >> <FILE_NAME>
ajoutera 3 lignes vides à la fin du fichier.
Au moins dans les versions GNU, simplement grep ''
ou awk 1
canonise son entrée, en ajoutant une nouvelle ligne finale si elle n'est pas déjà présente. Ils copient le fichier dans le processus, ce qui prend du temps s'il est volumineux (mais la source ne devrait pas être trop volumineuse de toute façon?) Et met à jour le modtime à moins que vous ne fassiez quelque chose comme
mv file old; grep '' <old >file; touch -r old file
(bien que cela puisse être correct sur un fichier que vous archivez parce que vous l'avez modifié) et il perd les liens durs, les autorisations non par défaut et les listes de contrôle d'accès, etc., sauf si vous êtes encore plus prudent.
Beaucoup de bonnes suggestions ici, mais une idée serait de supprimer une nouvelle ligne et d'en ajouter une pour que vous sachiez que vous ne les ajoutez pas continuellement:
Prenez le fichier "foo":
Supprimez une nouvelle ligne s'il y en a une:
truncate -s $(($(stat -c '%s' foo)-1)) foo
Ajoutez-en ensuite un:
sed -i -e '$a\' foo
Par conséquent, foo contiendra toujours au moins une nouvelle ligne.
ou queue le fichier et recherchez une nouvelle ligne, si elle ne contient pas ajouter un ..
grep -q "[^0-9a-z-]" <<< $(tail -1 ./foo) && echo " " >> ./foo
Cela fonctionne dans AIX ksh:
lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
echo "/n" >> *filename*
fi
Dans mon cas, si le fichier ne contient pas la nouvelle ligne, la commande wc
renvoie une valeur de 2
et nous écrivons une nouvelle ligne.