web-dev-qa-db-fra.com

Comment ajouter une nouvelle ligne à la fin d'un fichier?

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?

208
k0pernikus

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.

46
Patrick Oscity

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
215
l0b0

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)

41
sr_

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. 
16
enzotib

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
15
Alexander

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.

12
Barefoot IO

La solution la plus rapide est:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. 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
    
  2. Fonctionne en frêne, bash, lksh, mksh, ksh93, attsh et zsh mais pas yash.

  3. Ne modifie pas l'horodatage du fichier s'il n'est pas nécessaire d'ajouter une nouvelle ligne.
    Toutes les autres solutions présentées ici modifient l'horodatage du fichier.
  4. Toutes les solutions ci-dessus sont valides POSIX.

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
8
Isaac

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.

7
sorontar

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 ..?

4
AD7six

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.

3
user157018

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.

3
MichalH

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 ...
2

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 .

1
kenorb

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.

0
masgo

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é).

0
friederbluemle

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

  • devrait être efficace dans la mesure où il ne crée aucun processus, ne lit qu'un octet pour chaque fichier et ne réécrit pas le fichier (ajoute simplement une nouvelle ligne)
  • ne cassera pas les liens symboliques/hardlinks ou n'affectera pas les métadonnées (aussi, les ctime/mtime ne sont mis à jour que lorsqu'une nouvelle ligne est ajoutée)
  • devrait fonctionner correctement même si le dernier octet est un NUL ou fait partie d'un caractère multi-octets.
  • devrait fonctionner correctement quels que soient les caractères ou les non-caractères que les noms de fichiers peuvent contenir
  • Doit traiter correctement les fichiers illisibles, inscriptibles ou non recherchables (et signaler les erreurs en conséquence)
  • Ne doit pas ajouter de nouvelle ligne aux fichiers vides (mais signale une erreur concernant une recherche non valide dans ce cas)

Vous pouvez l'utiliser par exemple comme:

that-script *.txt

ou:

git ls-files -z | xargs -0 that-script
0
Stéphane Chazelas

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.

0
cipher

echo $'' >> <FILE_NAME> ajoutera une ligne vierge à la fin du fichier.

echo $'\n\n' >> <FILE_NAME> ajoutera 3 lignes vides à la fin du fichier.

0
user247137

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.

0
dave_thompson_085

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
0
Mike Q

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.

0
Daemoncan