Comment supprimer toutes les lignes d'un fichier de moins de 6 caractères?

17

J'ai un fichier contenant environ 10 millions de lignes.

Je souhaite supprimer toutes les lignes du fichier contenant moins de six caractères.

Comment puis-je faire cela?

Dis moi pourquoi
la source
Cette question n'est-elle pas plus adaptée à Stackoverflow?
user1073075
2
@ user1073075 c'est parfaitement sur le sujet ici.
Seth

Réponses:

30

Il y a plusieurs façons de procéder.

En utilisant grep:

grep -E '^.{6,}$' file.txt >out.txt

Contiendra désormais out.txtdes lignes de six caractères ou plus.

Inverse:

grep -vE '^.{,5}$' file.txt >out.txt

Utilisation sed, suppression de lignes de longueur 5 ou moins:

sed -r '/^.{,5}$/d' file.txt

Inverser, imprimer des lignes de longueur six ou plus:

sed -nr '/^.{6,}$/p' file.txt 

Vous pouvez enregistrer la sortie dans un fichier différent en utilisant l' >opérateur comme grepou modifier le fichier sur place en utilisant l' -ioption de sed:

sed -ri.bak '/^.{6,}$/' file.txt 

Le fichier d'origine sera sauvegardé en tant que file.txt.baket le fichier modifié sera file.txt.

Si vous ne souhaitez pas conserver de sauvegarde:

sed -ri '/^.{6,}$/' file.txt

Utiliser shell, Slower, Don't do this , c'est juste pour montrer une autre méthode:

while IFS= read -r line; do [ "${#line}" -ge 6 ] && echo "$line"; done <file.txt

L' utilisation python, encore plus lent que grep, sed:

#!/usr/bin/env python2
with open('file.txt') as f:
    for line in f:
        if len(line.rstrip('\n')) >= 6:
            print line.rstrip('\n')

Mieux utiliser la compréhension des listes pour être plus Pythonic:

#!/usr/bin/env python2
with open('file.txt') as f:
     strip = str.rstrip
     print '\n'.join([line for line in f if len(strip(line, '\n')) >= 6]).rstrip('\n')
heemayl
la source
Yay! J'espérais une réponse python =)
TellMeWhy
@DevRobot I see..then check out list comprehension i added, be more Pythonic ..
heemayl
1
De plus, @DevRobot n'est pas sûr que python soit plus lent sur les fichiers volumineux, lorsque la première option est utilisée. En fait, je suis presque sûr que python est plus rapide sur des millions de lignes, car il lit par ligne.
Jacob Vlijm
1
Le deuxième exemple python lit le fichier entier en mémoire avant de faire la jointure. Je pense que le premier exemple de python est meilleur dans ce cas.
Holloway
La lecture par lignes est nécessairement plus lente car les fichiers ne sont pas structurés comme ça. Vous devez quand même lire un bloc à l'avance et rechercher une nouvelle ligne avec des possibilités réduites de parallélisation, puis ne renvoyer que la chaîne partielle. Vous avez besoin d'un tampon circulaire. Vous devez allouer de la mémoire dynamiquement si vous ne savez pas combien de temps les lignes peuvent être.
The Vee
19

C'est très simple:

grep ...... inputfile > resultfile   #There are 6 dots

Ceci est extrêmement efficace, car grepil n'essaiera pas d'analyser plus que ce dont il a besoin, ni d'interpréter les caractères de quelque manière que ce soit: il envoie simplement une ligne (entière) à stdout (que le shell redirige ensuite vers le fichier de résultats) dès qu'il a vu 6 les caractères sur cette ligne ( .dans un contexte d'expression régulière correspond à n'importe quel caractère).

Ainsi, grep ne sortira que les lignes ayant 6 (ou plus) caractères, et les autres ne sont pas sorties par grep, donc elles ne parviennent pas au fichier de résultats.

Olivier Dulac
la source
14

Solution n ° 1: utiliser C

Méthode la plus rapide: compilez et exécutez ce programme C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_BUFFER_SIZE 1000000

int main(int argc, char *argv[]) {
    int length;

    if(argc == 3)
        length = atoi(argv[2]);
    else
        return 1;

    FILE *file = fopen(argv[1], "r");

    if(file != NULL) {
        char line[MAX_BUFFER_SIZE];

        while(fgets(line, sizeof line, file) != NULL) {
            char *pos;

            if((pos = strchr(line, '\n')) != NULL)
                *pos = '\0';
            if(strlen(line) >= length)
                printf("%s\n", line);
        }

        fclose(file);
    }
    else {
        perror(argv[1]);
        return 1;
    }

    return 0;
}

Compilez avec gcc program.c -o program, exécutez avec ./program file line_length(où file= chemin d'accès au fichier et line_length= longueur de ligne minimale, dans votre cas 6; la longueur de ligne maximale est limitée à des 1000000caractères par ligne; vous pouvez modifier cela en modifiant la valeur de MAX_BUFFER_SIZE).

(Astuce pour remplacer \npar \0trouvé ici .)

Comparaison avec toutes les autres solutions proposées à cette question à l'exception de la solution shell (test exécuté sur un fichier ~ 91MB avec 10M lignes avec une longueur moyenne de 8 caractères):

time ./foo file 6

real    0m1.592s
user    0m0.712s
sys 0m0.160s

time grep ...... file

real    0m1.945s
user    0m0.912s
sys 0m0.176s

time grep -E '^.{6,}$'

real    0m2.178s
user    0m1.124s
sys 0m0.152s

time awk 'length>=6' file

real    0m2.261s
user    0m1.228s
sys 0m0.160s

time perl -lne 'length>=6&&print' file

real    0m4.252s
user    0m3.220s
sys 0m0.164s

sed -r '/^.{,5}$/d' file >out

real    0m7.947s
user    0m7.064s
sys 0m0.120s

./script.py >out
real    0m8.154s
user    0m7.184s
sys 0m0.164s

Solution n ° 2: en utilisant AWK:

awk 'length>=6' file
  • length>=6: si length>=6renvoie VRAI, imprime l'enregistrement en cours.

Solution n ° 3: utiliser Perl:

perl -lne 'length>=6&&print' file
  • Si lenght>=6renvoie VRAI, imprime l'enregistrement en cours.

% cat file
a
bb
ccc
dddd
eeeee
ffffff
ggggggg
% ./foo file 6
ffffff
ggggggg
% awk 'length>=6' file   
ffffff
ggggggg
% perl -lne 'length>=6&&print' file
ffffff
ggggggg
kos
la source
1
Croyez-moi .. j'attendais votre awk solution ..
heemayl
2
@heemayl Et je n'ai pas vu la question immédiatement, donc je savais que si vous étiez en ligne, vous auriez été plus rapide. J'ai dû supprimer ma sedsolution (ça arrive, je sais). XD
kos
Quel est l'intérêt de la posvariable? Je comprends qu'il retourne un pointeur sur le caractère lineavec un caractère de nouvelle ligne, mais vous ne semblez jamais l'utiliser. Et si vous ne le trouvez pas, vous le définissez simplement égal à \0.
user1717828
@ user1717828 Si je le trouve , je le remplace par \0( strchr()renvoie un pointeur NULL si le caractère n'est pas trouvé). Le point consiste à remplacer chaque nouvelle ligne à la fin de chaque ligne par \0afin que la nouvelle ligne ne soit jamais comptée par strlen(): c'est pour que la longueur puisse toujours être comparée à 6, indépendamment d'une nouvelle ligne potentiellement manquante sur la dernière ligne. Traiter différemment seule la dernière ligne serait beaucoup plus efficace, je sais. Je mettrai probablement cela à jour plus tard.
kos
1
@tripleee L'idée était d'ajouter une solution utile pour quelque chose de plus qu'un travail ponctuel, ou pour des fichiers encore plus gros, mais : j'ai testé la grepsolution sur le même fichier et c'est en fait plus rapide (probablement parce que ce strlen()n'est pas la meilleure idée ici) . Je vais essayer d'utiliser une getchar()boucle afin de ne vérifier que le premier caractère N à la place, je suppose que cela devrait l'améliorer visiblement. Et oui, toute ligne au-dessus de la longueur du tampon est simplement coupée à la longueur du tampon.
kos
2

Vous pouvez utiliser Vim en mode Ex:

ex -sc 'v/\v.{6}/d' -cx file
  1. \v allume la magie

  2. .{6} rechercher des lignes avec 6 caractères ou plus

  3. v inverser la sélection

  4. d supprimer

  5. x sauver et fermer

Steven Penny
la source
1

Solution Ruby:

$ cat input.txt                                                                                                          
abcdef
abc
abcdefghijk

$ ruby -ne 'puts $_ if $_.chomp.length() >= 6 ' < input.txt                                                              
abcdef
abcdefghijk

Idée simple: rediriger le fichier vers stdin de ruby ​​et imprimer la ligne depuis stdin uniquement si sa longueur est supérieure ou égale à 6

Sergiy Kolodyazhnyy
la source