En utilisant les systèmes de contrôle de version, je suis agacé par le bruit lorsque le diff dit No newline at end of file
.
Alors je me demandais: comment ajouter une nouvelle ligne à la fin d'un fichier pour se débarrasser de ces messages?
En utilisant les systèmes de contrôle de version, je suis agacé par le bruit lorsque le diff dit No newline at end of file
.
Alors je me demandais: comment ajouter une nouvelle ligne à la fin d'un fichier pour se débarrasser de ces messages?
Réponses:
Pour désinfecter de manière récursive un projet, j'utilise cet 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 faut un motif facultatif comme paramètre supplémentaire, ce qui peut être utile dans certains cas si vous souhaitez limiter l'opération à certains fichiers / répertoires. Au lieu de cela, vous pouvez utiliser find -print0 ...
des programmes similaires pour répertorier les fichiers affectés - assurez-vous simplement qu’il émet des NUL
entrées délimitées.
while IFS= read -rd '' f; do ... done
effectue une itération dans les entrées, en gérant en toute sécurité les noms de fichiers qui incluent des espaces et / ou des nouvelles lignes.
tail -c1 < "$f"
lit le dernier caractère d'un fichier.
read -r _
quitte avec un statut de sortie différent de zéro s'il manque une nouvelle ligne.
|| 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.
find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
git ls-files
qui vous préservera néanmoins de la modification de fichiers qui ne sont pas suivis dans le contrôle de version.
IFS=
Ajouter le séparateur est utile pour préserver les espaces blancs environnants. Les entrées terminées par un caractère nul ne sont pertinentes que si vous avez des fichiers ou des répertoires avec une nouvelle ligne dans leur nom, ce qui semble assez poussé, mais c'est la manière la plus correcte de gérer le cas générique, je suis d'accord. Juste une petite mise en garde: l' -d
option to read
n'est pas disponible dans POSIX sh.
tail -n1 < "$f"
pour éviter les problèmes avec les noms de fichiers qui commencent par -
( tail -n1 -- "$f"
ne fonctionne pas pour le fichier appelé -
). Vous voudrez peut-être préciser que la réponse est maintenant spécifique à zsh / bash.
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'y aura pas d'autre 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
man sed
: $ Match the last line.
mais peut-être que cela ne fonctionne que par accident. Votre solution fonctionne également.
$
. Dans une expression rationnelle, comme avec la forme /<regex>/
, il a la signification habituelle de "correspondance de fin de ligne". Autrement, utilisé comme adresse, sed lui donne le sens spécial "dernière ligne du fichier". Le code fonctionne parce que sed ajoute par défaut une nouvelle ligne à sa sortie si elle n'y est pas déjà. Le code "$ a \" dit simplement "correspond à la dernière ligne du fichier et n'y ajoute rien". Mais implicitement, sed ajoute la nouvelle ligne à chaque ligne traitée (telle que cette $
ligne), si ce n’est pas déjà fait.
/regex/
lui donne un sens différent. Les pages de manuel FreeBSD sont un peu plus informatives, je pense: freebsd.org/cgi/man.cgi?query=sed
Regarde:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
alors echo "" >> noeol-file
devrait faire l'affaire. (Ou avez-vous eu l'intention de demander d'identifier ces fichiers et de les corriger?)
edit a supprimé le ""
de echo "" >> foo
(voir le commentaire de @ yuyichao)
edit2 l'a ajouté à ""
nouveau ( mais voir le commentaire de @Keith Thompson)
""
n'est pas nécessaire (du moins pour bash) et tail -1 | wc -l
peut être utilisé pour trouver le fichier sans nouvelle ligne à la fin
""
n'est pas nécessaire pour bash, mais j'ai déjà vu des echo
implémentations qui n'impriment rien lorsqu'elles sont appelées sans arguments (bien qu'aucune de celles que je puisse trouver maintenant ne le fasse). echo "" >> noeol-file
est probablement un peu plus robuste. printf "\n" >> noeol-file
est encore plus.
csh
« s echo
est celui connu à rien de sortie lorsqu'ils ne sont pas passé aucun argument. Mais alors, si nous voulons soutenir des coquilles non-Bourne, nous devrions le faire echo ''
au lieu de ce echo ""
que echo ""
nous ferions ""<newline>
avec rc
ou es
par exemple.
tcsh
, contrairement à cela csh
, affiche une nouvelle ligne quand elle est invoquée sans arguments - quel que soit le réglage de $echo_style
.
Une autre solution utilisant ed
. Cette solution n'affecte que la dernière ligne et uniquement s'il \n
manque:
ed -s file <<< w
Cela fonctionne essentiellement en ouvrant le fichier pour l’éditer à travers un script, le script est la seule w
commande qui réécrit le fichier sur le disque. Il est basé sur cette phrase trouvée dans la ed(1)
page de manuel:
LIMITES (...) Si un fichier texte (non binaire) n’est pas terminé par un caractère de nouvelle ligne, puis ed en ajoute un à la lecture / écriture. Dans le cas d'un binaire file, ed n’ajoute pas de nouvelle ligne en lecture / écriture.
Un moyen simple, portable et compatible POSIX d’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’ensemble du fichier; il peut simplement chercher à EOF et travailler à partir de là.
Cette approche n'a pas non plus besoin de créer des fichiers temporaires derrière votre dos (par exemple, sed -i), les liens physiques ne sont donc 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 une nouvelle ligne, tail le renvoie, puis la substitution de commande 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 l'écho ne s'exécute pas à nouveau. Cela 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.
yash
si le dernier caractère du fichier est un caractère multi-octets (dans les environnements locaux UTF-8, par exemple) ou si l'environnement local est C et que le dernier octet du fichier est défini sur le huitième bit. Avec les autres shells (sauf zsh), cela n’ajouterait pas de nouvelle ligne si le fichier se terminait par un octet NUL (mais cela signifierait que l’entrée ne serait pas du texte même après l’ajout d’une nouvelle ligne).
Ajouter newline indépendamment de:
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
echo ""
semble plus robuste que echo -n '\n'
. Ou vous pouvez utiliserprintf '\n'
La solution la plus rapide est:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
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 ash, bash, lksh, mksh, ksh93, attsh et zsh mais pas yash.
Si vous avez besoin d'une solution portable pour yash (et tous les autres shells listés ci-dessus), la complexité 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 vérifier 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 vérifier si la valeur d'octet est une nouvelle ligne, en fonction du shell habituel, la suppression habituelle d'une nouvelle ligne dans une extension de commande échoue (par exemple) dans yash, lorsque le dernier caractère du fichier est un caractère UTF. 8 valeur.
Le moyen correct, correct, compatible avec POSIX et tout le monde (raisonnable) de rechercher 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 les résultats ci-dessus, vous 0A
obtiendrez un test robuste.
Il est utile d'éviter d'ajouter une nouvelle ligne à un fichier sinon vide.
Fichier qui ne fournira pas un dernier caractère 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 lit que le dernier octet (cherche à EOF). Peu importe si le fichier est gros. Ensuite, ajoutez uniquement un octet si nécessaire.
Aucun fichier temporaire nécessaire ni utilisé. Aucun lien physique n'est affecté.
Si ce test est exécuté deux fois, il n’ajoutera pas de nouvelle ligne.
xxd
ne hexdump
sont des utilitaires POSIX. Dans la boîte à outils POSIX, il od -An -tx1
faut obtenir la valeur hexadécimale d'un octet.
Vous feriez mieux de corriger l'éditeur de l'utilisateur qui a édité le fichier en dernier. Si vous êtes la dernière personne à avoir modifié le fichier - quel éditeur utilisez-vous, je devine textmate ..?
emacs
n'ajoute pas de nouvelle ligne à la fin du fichier.
(setq require-final-newline 'ask)
dans ma.emacs
Si vous souhaitez simplement ajouter rapidement une nouvelle ligne lors du traitement d'un pipeline, utilisez ceci:
outputting_program | { cat ; echo ; }
il est également conforme à POSIX.
Ensuite, vous pouvez bien sûr le rediriger vers un fichier.
cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Pourvu qu'il n'y ait pas de NULL en entrée:
paste - <>infile >&0
... suffirait toujours pour ajouter une nouvelle ligne à la fin d'un infile s'il n'en avait pas déjà un. Et il suffit de lire le fichier d’entrée une seule fois pour que tout se passe bien.
paste infile 1<> infile
place.
Bien que cela 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 lit le dernier octet pour chaque fichier afin de déterminer si le fichier se termine par une nouvelle ligne ou non. C'est très rapide car cela évite de lire tout le contenu de chaque fichier. Il affiche une ligne pour chaque fichier lu, précédé du préfixe "erreur:" si une erreur survient, "vide:" si le fichier est vide (ne se termine pas par une nouvelle ligne!), "EOL:" ("fin de line ") 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 contenant des nouvelles lignes. Si vous êtes sur 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, il vous faudrait toujours trouver un moyen d’encoder les noms de fichiers avec des nouvelles lignes dans la sortie (à gauche comme exercice pour le lecteur).
La sortie peut être filtrée, si vous le souhaitez, pour ajouter une nouvelle ligne à ces fichiers qui n'en ont pas, le plus simplement avec
echo >> "$filename"
L'absence d'une nouvelle ligne peut entraîner des erreurs dans les scripts, car certaines versions de shell et d'autres utilitaires ne gèrent pas correctement une nouvelle ligne manquante lors de la lecture d'un tel fichier.
D'après mon expérience, l'absence d'un saut de ligne final est due à l'utilisation de divers utilitaires Windows pour éditer des fichiers. Je n'ai jamais vu vim causer une fin de ligne manquante lors de l'édition d'un fichier, bien qu'il en fasse rapport.
Enfin, il existe des scripts beaucoup plus courts (mais plus lents) qui peuvent suivre leurs entrées de nom de fichier pour imprimer les fichiers ne se terminant 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 l’ait déjà.
Alors essayez soit:
vi -ecwq foo.txt
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 réparer «Pas de nouvelle ligne à la fin du fichier» pour un grand nombre de fichiers? à SO
Pourquoi c'est si important? Pour garder nos fichiers compatibles POSIX .
Pour appliquer la réponse acceptée à tous les fichiers du répertoire en cours (plus les sous-répertoires):
$ find . -type f -exec sed -i -e '$a\' {} \;
Cela fonctionne sous Linux (Ubuntu). Sous OS X, vous devez probablement utiliser -i ''
(non testé).
find .
répertorie tous les fichiers, y compris les fichiers dans .git
. À exclure:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
Au moins dans les versions de GNU, son entrée est simplement grep ''
ouawk 1
canonisé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 le source ne devrait pas être trop volumineux pour être lu 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 qu'il perd les liens en dur, les autorisations non définies par défaut et les ACL, etc., à moins que vous ne soyez encore plus prudent.
grep '' file 1<> file
, bien que cela lise et écrit le fichier complètement.
Cela fonctionne sous AIX ksh:
lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
echo "/n" >> *filename*
fi
Dans mon cas, si la nouvelle ligne manque dans le fichier, la wc
commande retourne une valeur de 2
et nous écrivons une nouvelle ligne.
En ajoutant à la 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 ceci dans le répertoire dans lequel 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.
Si votre fichier se termine par des fins de ligne Windows\r\n
et que vous êtes sous Linux, vous pouvez utiliser cette sed
commande. Cela ne fait qu'ajouter \r\n
à la dernière ligne si ce n'est déjà fait:
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 rationnelle de recherche ne correspondra pas, donc rien ne se passera.
Vous pouvez écrire un fix-non-delimited-line
script comme:
#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
if sysopen -rwu0 -- "$file"; then
if sysseek -w end -1; then
read -r x || print -u0
else
syserror -p "Can't seek in $file before the last byte: "
ret=1
fi
else
ret=1
fi
done
exit $ret
Contrairement à certaines des solutions données ici, il
Vous pouvez l'utiliser par exemple comme:
that-script *.txt
ou:
git ls-files -z | xargs -0 that-script
POSIXly, vous pourriez faire quelque chose de fonctionnellement équivalent avec
export LC_ALL=C
ret=0
for file do
[ -s "$file" ] || continue
{
c=$(tail -c 1 | od -An -vtc)
case $c in
(*'\n'*) ;;
(*[![:space:]]*) printf '\n' >&0 || ret=$?;;
(*) ret=1;; # tail likely failed
esac
} 0<> "$file" || ret=$? # record failure to open
done