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

191

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?

k0pernikus
la source
1
voir aussi so / q / 10082204/155090
RubyTuesdayDONO le
1
Belle solution en bas qui nettoie tous les fichiers de manière récursive. Réponse de @Patrick Oscity
Qwerty
À l'avenir, les éditeurs de texte disposent souvent d'options pour s'assurer qu'il existe une nouvelle ligne que vous et vos collaborateurs pourriez utiliser pour rester propre.
Nick T

Réponses:

45

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 -zré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 NULentré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.

Patrick Oscity
la source
Vous pouvez également le faire comme ceci si vous souhaitez simplement nettoyer un sous-ensemble de vos fichiers:find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Per Lundberg
@ StéphaneChazelas bonnes suggestions, va essayer d'intégrer cela dans ma réponse.
Patrick Oscity
@ PerLundberg, vous pouvez également transmettre un modèle git ls-filesqui vous préservera néanmoins de la modification de fichiers qui ne sont pas suivis dans le contrôle de version.
Patrick Oscity
@ StéphaneChazelas 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' -doption to readn'est pas disponible dans POSIX sh.
Patrick Oscity
Oui, d'où mes zsh / bash . Voir aussi mon utilisation de 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.
Stéphane Chazelas
203

Ici vous allez :

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
l0b0
la source
1
@jwd: De man sed: $ Match the last line.mais peut-être que cela ne fonctionne que par accident. Votre solution fonctionne également.
l0b0
1
Votre solution est également plus élégante et je l’ai testée et validée , mais comment peut-elle fonctionner? Si $correspond à la dernière ligne, pourquoi n'ajoute-t-il pas une nouvelle ligne à une chaîne qui contient déjà une nouvelle ligne?
l0b0
27
Il y a deux significations différentes de $. 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.
Jwd
1
Concernant la page de manuel: La citation dont vous parlez se trouve dans la section "Adresses". Le mettre à l'intérieur /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
jwd
2
Si le fichier se termine déjà par un retour à la ligne, cela ne le change pas, mais le réécrit et met à jour son horodatage. Cela peut ou peut ne pas avoir d'importance.
Keith Thompson
39

Regarde:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

alors echo "" >> noeol-filedevrait 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)

sr_
la source
4
le ""n'est pas nécessaire (du moins pour bash) et tail -1 | wc -lpeut être utilisé pour trouver le fichier sans nouvelle ligne à la fin
yuyichao
5
@yuyichao: Cela ""n'est pas nécessaire pour bash, mais j'ai déjà vu des echoimplé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-fileest probablement un peu plus robuste. printf "\n" >> noeol-fileest encore plus.
Keith Thompson
2
@KeithThompson, csh« s echoest 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 rcou espar exemple.
Stéphane Chazelas
1
@ StéphaneChazelas: Et tcsh, contrairement à cela csh, affiche une nouvelle ligne quand elle est invoquée sans arguments - quel que soit le réglage de $echo_style.
Keith Thompson le
16

Une autre solution utilisant ed. Cette solution n'affecte que la dernière ligne et uniquement s'il \nmanque:

ed -s file <<< w

Cela fonctionne essentiellement en ouvrant le fichier pour l’éditer à travers un script, le script est la seule wcommande 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.
enzotib
la source
1
Cela n'ajoute pas de nouvelle ligne pour moi.
Olhovsky
4
Travaille pour moi; il imprime même "Newline annexé" (ed-1.10-1 sur Arch Linux).
Stefan Majewsky
12

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.

Pieds nus IO
la source
1
Notez que cela ne fonctionne pas yashsi 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).
Stéphane Chazelas
1
@ StéphaneChazelas Une solution pour yash a été ajoutée .
sorontar
1
Est-il possible de l'exécuter pour chaque fichier d'un dossier et de sous-dossiers?
Qwerty
12

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
Alexandre
la source
1
Je n'utiliserais pas la version python dans aucune sorte de boucle en raison du temps de démarrage lent de python. Bien sûr, vous pouvez faire la boucle en python si vous le souhaitez.
Kevin Cox
2
Le temps de démarrage de Python est de 0,03 seconde ici. Estimez-vous vraiment que cela pose problème?
Alexandre
3
Le temps de démarrage a de l'importance si vous appelez python dans une boucle, c'est pourquoi j'ai dit envisagez de faire la boucle en python. Ensuite, vous ne payez qu'une fois le coût de démarrage. Pour moi, la moitié du coût de la mise en route représente plus de la moitié du temps total du snipit, ce que je considérerais comme un surcoût substantiel. (Encore une fois, sans importance si vous ne faites qu'un petit nombre de fichiers)
Kevin Cox
2
echo ""semble plus robuste que echo -n '\n'. Ou vous pouvez utiliserprintf '\n'
Keith Thompson
2
Cela a bien fonctionné pour moi
Daniel Gomez Rico
8

La solution la plus rapide est:

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

  1. Est vraiment rapide.
    Sur un fichier de taille moyenne, seq 99999999 >filecela 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 ash, 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 valables POSIX.

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
Isaac
la source
7

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 0Aobtiendrez 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.

sorontar
la source
1
@crw Je crois que cela ajoute des informations utiles.
sorontar
2
Notez que ni xxdne hexdumpsont des utilitaires POSIX. Dans la boîte à outils POSIX, il od -An -tx1faut obtenir la valeur hexadécimale d'un octet.
Stéphane Chazelas
@ StéphaneChazelas S'il vous plaît poster cela comme une réponse; Je suis venu ici à la recherche de ce commentaire trop souvent :)
kelvin
@kelvin, j'ai mis à jour ma réponse
Stéphane Chazelas le
Notez que POSIX ne garantit pas que la valeur de LF soit 0x0a. Il y a encore des systèmes POSIX où ce n'est pas le cas (ceux basés sur EBCDIC) bien qu'ils soient extrêmement rares de nos jours.
Stéphane Chazelas le
4

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

AD7six
la source
2
Vim est l'éditeur en question. Mais en général, vous avez raison, je ne devrais pas seulement régler les symptômes;)
k0pernikus
6
pour vim, vous devez vous débrouiller et faire la danse des fichiers binaires lors de la sauvegarde pour que vim n’ajoute pas de nouvelle ligne à la fin du fichier - ne faites pas cette danse. OU, pour corriger simplement les fichiers existants, ouvrez-les dans vim et enregistrez-le. Vim "corrigera" la nouvelle ligne manquante pour vous (peut être facilement scripté pour plusieurs fichiers)
AD7six
3
Mon emacsn'ajoute pas de nouvelle ligne à la fin du fichier.
enzotib
2
Merci pour le commentaire @ AD7six, je continue à recevoir des rapports fantômes de diffs lorsque je commets des choses, sur la façon dont le fichier original ne comporte pas de nouvelle ligne à la fin. Peu importe la façon dont je modifie un fichier avec vim, je ne peux pas l'obtenir sans y mettre une nouvelle ligne. Donc, c'est juste que vim le fait.
Steven Lu
1
@enzotib: J'ai (setq require-final-newline 'ask)dans ma.emacs
Keith Thompson le
3

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.

MichalH
la source
2
Le fait que je puisse utiliser cela dans un pipeline est utile. Cela me permet de compter le nombre de lignes d'un fichier CSV, sans compter l'en-tête. Et cela aide à obtenir un décompte précis des lignes sur les fichiers Windows qui ne se terminent pas par un retour à la ligne ou un retour à la ligne. cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Kyle Tolle
3

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.

Toby Speight
la source
Cela ne fonctionnera pas comme ça car stdin et stdout partagent la même description de fichier ouvert (donc le curseur dans le fichier). Vous auriez besoin à la paste infile 1<> infileplace.
Stéphane Chazelas
2

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 ...
jrw32982
la source
1

Les éditeurs vi/ vim/ exajoutent 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 .

Kenorb
la source
0

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

friederbluemle
la source
4
Notez que find .répertorie tous les fichiers, y compris les fichiers dans .git. À exclure:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
friederbluemle le
J'aurais aimé lire ce commentaire / y avoir pensé avant de le lancer. Tant pis.
kstev
0

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.

dave_thompson_085
la source
Ou juste grep '' file 1<> file, bien que cela lise et écrit le fichier complètement.
Stéphane Chazelas
-1

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 wccommande retourne une valeur de 2et nous écrivons une nouvelle ligne.

Daemoncan
la source
Les commentaires seront présentés sous forme de votes à la hausse ou à la baisse, ou des commentaires vous seront demandés pour décrire davantage vos réponses / questions, inutile de le demander dans le corps de la réponse. Gardez le point, bienvenue à stackexchange!
k0pernikus
-1

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.

chiffrer
la source
-1

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

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

utilisateur247137
la source
Le StackExchange a une mise en forme amusante, je l'ai corrigé pour vous :-)
user259412 Le
-1

Si votre fichier se termine par des fins de ligne Windows\r\n et que vous êtes sous Linux, vous pouvez utiliser cette sedcommande. 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\nalors l'expression rationnelle de recherche ne correspondra pas, donc rien ne se passera.

masgo
la source
-1

Vous pouvez écrire un fix-non-delimited-linescript 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

  • devrait être efficace en ce sens qu'il ne déclenche aucun processus, ne lit qu'un octet pour chaque fichier et ne réécrit pas le fichier (ajoute simplement une nouvelle ligne)
  • ne rompt pas les liens symboliques / durs et n'affecte pas les métadonnées (de plus, les commandes ctime / mtime ne sont mises à 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 non-caractères que les noms de fichiers peuvent contenir
  • Devrait gérer correctement les fichiers illisibles ou non lisibles ou non lisibles (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

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
Stéphane Chazelas
la source