web-dev-qa-db-fra.com

Comment supprimer les lignes en double dans un fichier texte?

Un énorme fichier texte (jusqu'à 2 Gio) contient environ 100 doublons exacts de chaque ligne (inutile dans mon cas, car le fichier est un tableau de données de type CSV).

Ce dont j'ai besoin, c'est de supprimer toutes les répétitions tout en (de préférence, mais cela peut être sacrifié pour une amélioration significative des performances) en maintenant l'ordre de séquence d'origine. Dans le résultat, chaque ligne doit être unique. S'il y avait 100 lignes égales (généralement les doublons sont répartis sur le fichier et ne seront pas voisins), il ne doit en rester qu'un du genre.

J'ai écrit un programme en Scala (considérez-le Java si vous ne connaissez pas Scala) pour l'implémenter. Mais peut-être existe-t-il des outils natifs plus rapides écrits en C capables de le faire plus rapidement?

MISE À JOUR: la solution awk '!seen[$0]++' filename Semblait fonctionner très bien pour moi tant que les fichiers étaient proches de 2 GiB ou plus petits mais maintenant que je dois nettoyer un fichier 8 GiB, il ne fonctionne pas ne fonctionne plus. Il semble prendre l'infini sur un Mac avec 4 GiB RAM et un PC Windows 7 64 bits avec 4 GiB RAM et 6 GiB swap vient de s'épuiser de la mémoire. Et je ne suis pas enthousiaste à l'idée de l'essayer sur Linux avec 4 GiB RAM compte tenu de cette expérience.

141
Ivan

Une solution awk vue sur #bash (Freenode):

awk '!seen[$0]++' filename
234
enzotib

Il existe une méthode simple (ce qui n'est pas évident) utilisant des utilitaires standard qui ne nécessite pas une grande mémoire sauf pour exécuter sort, qui dans la plupart des implémentations a des optimisations spécifiques pour les fichiers volumineux (un bon algorithme de tri externe) . Un avantage de cette méthode est qu'elle ne boucle que sur toutes les lignes à l'intérieur des utilitaires spéciaux, jamais à l'intérieur des langages interprétés.

<input nl -b a -s : |           # number the lines
sort -t : -k 2 -u |             # sort and uniquify ignoring the line numbers
sort -t : -k 1n |               # sort according to the line numbers
cut -d : -f 2- >output          # remove the line numbers

Si toutes les lignes commencent par un caractère non blanc, vous pouvez vous dispenser de certaines des options:

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output

Pour une grande quantité de duplication, une méthode qui ne nécessite que le stockage d'une seule copie de chaque ligne en mémoire fonctionnera mieux. Avec une surcharge d'interprétation, il y a un script awk très concis pour cela (déjà publié par enzotib ):

<input awk '!seen[$0]++'

Moins concis: !seen[$0] {print} {seen[$0] += 1}, c'est-à-dire imprimer la ligne actuelle si elle n'a pas encore été vue, puis incrémenter le compteur seen pour cette ligne (les variables ou éléments de tableau non initialisés ont la valeur numérique 0).

Pour les lignes longues, vous pouvez économiser de la mémoire en ne conservant qu'une somme de contrôle non falsifiable (par exemple, un résumé cryptographique) de chaque ligne. Par exemple, avec SHA-1, vous n'avez besoin que de 20 octets plus une surcharge constante par ligne. Mais le calcul des résumés est plutôt lent; cette méthode ne gagnera que si vous avez un processeur rapide (en particulier un avec un accélérateur matériel pour calculer les condensés) et pas beaucoup de mémoire par rapport à la taille du fichier et des lignes suffisamment longues. Aucun utilitaire de base ne vous permet de calculer une somme de contrôle pour chaque ligne; vous devrez supporter la surcharge d'interprétation de Perl/Python/Ruby /… ou écrire un programme compilé dédié.

<input Perl -MDigest::MD5 -ne '$seen{Digest::MD5::md5($_)}++ or print' >output
sort -u big-csv-file.csv > duplicates-removed.csv

Notez que le fichier de sortie sera trié.

26

En supposant que vous pouvez vous permettre de conserver autant que le fichier dédupliqué en mémoire (si vos données sont effectivement dupliquées par un facteur de 100, cela devrait représenter environ 20 Mo + frais généraux), vous pouvez le faire très facilement avec Perl.

$ Perl -ne 'print unless $dup{$_}++;' input_file > output_file

Cela préserve également l'ordre.

Vous pouvez extraire le nombre d'occurrences de chaque ligne du %dup hash si vous le souhaitez, en bonus gratuit supplémentaire.

Si vous préférez awk, cela devrait le faire aussi (même logique que la version Perl, même ordre, mêmes données rassemblées dans la variable dup):

$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file
19
Mat

Comme aucune autre réponse n'a fourni de support sur place, en voici une:

gawk -i inplace '!a[$0]++' file
7
rindeal

Vous pouvez utiliser uniqhttp://www.computerhope.com/unix/uuniq.htm

uniq signale ou filtre les lignes répétées dans un fichier.

3
Mahmoud Zalt

doublures Python One:

python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile
2
Rahul Patil

Aucune des réponses ici n'a fonctionné pour moi sur mon Mac, j'ai donc écrit un simple script python qui fonctionne pour moi. J'ignore les espaces de début/fin et ne me soucie pas non plus de la consommation de mémoire.

import sys

inputfile = sys.argv[1]
outputfile = sys.argv[2]

with open(inputfile) as f:
    content = f.readlines()

content = [x.strip() for x in content]

my_list = list(set(content))

with open(outputfile, 'w') as output:
    for item in my_list:
        output.write("%s\n" % item)

Enregistrez ce qui précède dans unique.py et exécutez comme ceci:

python unique.py inputfile.txt outputfile.txt
0
Jared