Comment supprimer plusieurs sauts de ligne à l'EOF?


25

J'ai des fichiers qui se terminent par un ou plusieurs sauts de ligne et doivent se terminer par un seul saut de ligne. Comment puis-je faire cela avec les outils Bash / Unix / GNU?

Exemple de mauvais fichier:

1\n
\n
2\n
\n
\n
3\n
\n
\n
\n

Exemple de fichier corrigé:

1\n
\n
2\n
\n
\n
3\n

En d'autres termes: il doit y avoir exactement une nouvelle ligne entre l'EOF et le dernier caractère non nouvelle ligne du fichier.

Implémentation de référence

Lisez le contenu du fichier, coupez une seule nouvelle ligne jusqu'à ce qu'il n'y ait plus deux nouvelles lignes à la fin, réécrivez-la:

#! /bin/python

import sys

with open(sys.argv[1]) as infile:
    lines = infile.read()

while lines.endswith("\n\n"):
    lines = lines[:-1]

with open(sys.argv[2], 'w') as outfile:
    for line in lines:
        outfile.write(line)

Clarification: Bien sûr, la tuyauterie est autorisée, si c'est plus élégant.

Réponses:


16
awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; nlstack=""; print;}' file

2
+1: les solutions d'awk sont (presque) toujours élégantes et lisibles!
Olivier Dulac

@OlivierDulac En effet. Quand j'ai vu la sedproposition, je pensais juste OMG ...
Hauke ​​Laging

1
cela ne fonctionne pas sur OSX Mavericks en utilisant le dernier awk disponible de Homebrew. Il contient des erreurs awk: illegal statement. brew install mawket changer la commande en mawkfonctionne cependant.
tjmcewan

@noname Je ne comprends même pas la question ...
Hauke ​​Laging

Tout awk dans lequel le script ne fonctionne pas est un awk mal cassé - arrêtez de l'utiliser et obtenez un nouvel awk parce que s'il ne peut pas faire cela, alors qui sait quel autre casse il a.
Ed Morton

21

À partir de scripts d'une ligne utiles pour sed .

# Delete all trailing blank lines at end of file (only).
sed -e :a -e '/^\n*$/{$d;N;};/\n$/ba' file

4
Merci, j'ai utilisé ce qui suit pour le faire en place pour plusieurs fichiers: find . -type f -name '*.js' -exec sed --in-place -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
jakub.g

@ jakub.g en place et récursif est exactement ce dont j'avais besoin. Je vous remercie.
Buttle Butkus

Pour ajouter à l'excellent commentaire de @ jakub.g, vous pouvez invoquer la commande comme celle-ci sur OS X:find . -type f -name '*.js' -exec sed -i '' -e :a -e '/^\n*$/{$d;N;};/\n$/ba' {} \;
davejagoda

18

Puisque vous avez déjà des réponses avec les outils les plus adaptés sed et awk; vous pouvez profiter du fait que les $(< file)bandes vides de fin sont supprimées.

a=$(<file); printf '%s\n' "$a" > file

Ce hack bon marché ne fonctionnerait pas pour supprimer les lignes vides de fin qui peuvent contenir des espaces ou d'autres caractères non imprimables, uniquement pour supprimer les lignes vides de fin. Cela ne fonctionnera pas non plus si le fichier contient des octets nuls.

Dans les shells autres que bash et zsh, utilisez $(cat file)plutôt que $(<file).


+1 pour signaler à quoi ressemble un bug: $ (<fichier) ne lit pas vraiment le fichier? pourquoi rejette-t-il les sauts de ligne? (il le fait, je viens de le tester, merci de l'avoir signalé!)
Olivier Dulac

2
@OlivierDulac $()supprime les nouvelles lignes de fin. C'est une décision de conception. Je suppose que cela facilitera l'intégration dans d'autres chaînes: ce echo "On $(date ...) we will meet."serait mal avec la nouvelle ligne que presque toutes les commandes shell produisent à la fin.
Hauke ​​Laging

@HaukeLaging: bon point, c'est probablement la source de ce comportement
Olivier Dulac

J'ai ajouté un cas particulier pour éviter annexant « \ n » pour vider les fichiers: [[ $a == '' ]] || printf '%s\n' "$a" >"$file".
davidchambers

Pour supprimer plusieurs sauts de ligne au début d'un fichier, insérez tac dans le processus (j'utilise gnu coreutils sur Mac, donc gtac pour moi):a=$(gtac file.txt); printf '%s\n' "$a" | gtac > file.txt
r_alex_hall


4

Cette question est étiquetée avec , mais personne n'a proposé de edsolution.

En voici un:

ed -s file <<'ED_END'
a

.
?^..*?+1,.d
w
ED_END

ou équivalent,

printf '%s\n' a '' . '?^..*?+1,.d' w | ed -s file

ed vous placera par défaut à la dernière ligne du tampon d'édition au démarrage.

La première commande ( a) ajoute une ligne vide à la fin du tampon (la ligne vide dans le script d'édition est cette ligne, et le point ( .) est juste pour revenir en mode commande).

La deuxième commande ( ?) recherche la ligne précédente la plus proche contenant quelque chose (même des espaces blancs), puis supprime tout à la fin du tampon de la ligne suivante.

La troisième commande ( w) réécrit le fichier sur le disque.

La ligne vide ajoutée protège le reste du fichier contre la suppression dans le cas où il n'y a pas de lignes vides à la fin du fichier d'origine.


3

Voici une solution Perl qui ne nécessite pas de lire plus d'une ligne en mémoire à la fois:

my $n = 0;
while (<>) {
    if (/./) {
        print "\n" x $n, $_;
        $n = 0;
    } else {
        $n++;
    }
}

ou, en une ligne:

perl -ne 'if (/./) { print "\n" x $n, $_; $n = 0 } else { $n++ }'

Cela lit le fichier une ligne à la fois et vérifie chaque ligne pour voir si contient un caractère non-newline. Si ce n'est pas le cas, il incrémente un compteur; si c'est le cas, il imprime le nombre de nouvelles lignes indiqué par le compteur, suivi de la ligne elle-même, puis réinitialise le compteur.

Techniquement, même la mise en mémoire tampon d'une seule ligne en mémoire n'est pas nécessaire; il serait possible de résoudre ce problème en utilisant une quantité constante de mémoire en lisant le fichier en morceaux de longueur fixe et en le traitant caractère par caractère à l'aide d'une machine à états. Cependant, je soupçonne que ce serait inutilement compliqué pour le cas d'utilisation typique.


1

Si votre fichier est suffisamment petit pour glisser en mémoire, vous pouvez utiliser ce

perl -e 'local($/);$f=<>; $f=~s/\n*$/\n/;print $f;' file

0

En python (je sais que ce n'est pas ce que vous voulez, mais c'est beaucoup mieux car il est optimisé, et un prélude à la version bash) sans réécrire le fichier et sans lire tout le fichier (ce qui est une bonne chose si le fichier est très grand):

#!/bin/python
import sys
infile = open(sys.argv[1], 'r+')
infile.seek(-1, 2)
while infile.read(1) == '\n':
  infile.seek(-2, 1)
infile.seek(1, 1)
infile.truncate()
infile.close()

Notez qu'il ne fonctionne pas sur les fichiers où le caractère EOL n'est pas '\ n'.


0

Une version bash, implémentant l'algorithme python, mais moins efficace car elle nécessite de nombreux processus:

#!/bin/bash
n=1
while test "$(tail -n $n "$1")" == ""; do
  ((n++))
done
((n--))
truncate -s $(($(stat -c "%s" "$1") - $n)) "$1"

0

Celui-ci est rapide à taper et, si vous connaissez sed, facile à retenir:

tac < file | sed '/[^[:blank:]]/,$!d' | tac

Il utilise le script sed pour supprimer les premières lignes vides des scripts d'une ligne utiles pour sed , référencés par Alexey, ci-dessus, et tac (reverse cat).

Dans un test rapide, sur un fichier de 18 Mo, 64 000 lignes, l'approche d'Alexey était plus rapide (0,036 contre 0,046 seconde).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.