Comment supprimer les lignes en double dans un fichier texte?

Un de mes gros fichiers 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 jai besoin est de supprimer toutes les répétitions tout en conservant (de préférence, mais cela peut être sacrifié pour une amélioration significative des performances) lordre de séquence dorigine. Dans le résultat, chaque ligne doit être unique. Sil y avait 100 lignes égales (généralement les doublons sont répartis dans le fichier et ne seront pas voisins), il ne doit en rester quune seule du genre.

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

MISE À JOUR: la solution awk "!seen[$0]++" filename me semblait très bien fonctionner tant que les fichiers étaient près de 2 Gio ou moins, mais maintenant que je dois nettoyer un fichier de 8 Gio, cela ne fonctionne plus. Il semble prendre linfini sur un Mac avec 4 Gio de RAM et un PC Windows 7 64 bits avec 4 Gio de RAM et le swap de 6 Gio manque juste de mémoire. Et je ne me sens pas enthousiaste à lidée de lessayer sous Linux avec 4 Gio de RAM étant donné cette expérience.

Commentaires

  • cela détruira votre commande mais, avez-vous essayé sort -u, je ne sais pas comment ou si cela peut fonctionner sur un fichier aussi volumineux
  • C nest souvent pas beaucoup plus rapide que Java, et si vous ‘ lexécutez (dans lordre) maintenant, il ‘ a de bonnes chances que ce soit ‘ ll terminer avant que vous nobteniez une réponse ici, implémentez-le, et il se termine; dans le désordre, sort -u sera probablement plus rapide.

Réponse

Une solution awk vue sur #bash (Freenode):

awk "!seen[$0]++" filename 

Commentaires

  • Je viens dessayer ceci sur un fichier 2G et cela a pris trois minutes sur mon ordinateur portable. Pas mal. Jai aussi essayé le nom de fichier uniq | awk ‘! vu [$ 0] ++ ‘, mais ce nétait ‘ aucun
  • @HashWizard: cette commande ne trie pas, mais élimine chaque occurrence suivante de la même ligne
  • Vous vous demandez comment fonctionne cette commande? – Voir ici: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams oui , cela fonctionne si elles sont distribuées aléatoirement.
  • préserver les nouvelles lignes ou les lignes avec des espaces awk '/^\s*?$/||!seen[$0]++'

Réponse

Il existe une méthode simple (ce qui ne veut pas dire évidente) 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 quelle ne boucle que sur toutes les lignes dans des utilitaires spéciaux, jamais dans 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 passer 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 de stocker quune seule copie de chaque ligne en mémoire fonctionnera mieux. Avec une certaine surcharge dinterprétation, il existe « un script awk très concis pour cela (déjà posté par enzotib ):

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

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

Pour les longues lignes, vous pouvez économiser de la mémoire en ne conservant quune somme de contrôle non-spoofable (par exemple un résumé cryptographique) de chaque ligne . Par exemple, en utilisant SHA-1, vous navez 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 CPU rapide (en particulier un avec un accélérateur matériel pour calculer les digests) 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 « auriez à supporter la surcharge dinterprétation de Perl / Python / Ruby /… ou écrire un programme compilé dédié.

<input perl -MDigest::MD5 -ne "$seen{Digest::MD5::md5($_)}++ or print" >output 

Commentaires

  • @Gilles Daprès votre explication de awk '!seen[$0]++', cela signifie-t-il que si awk voit 2 lignes en double, il gardera toujours la première et ignorera tout les suivants? (Ou il gardera le dernier?)
  • @ user779159 Il garde le premier: chaque ligne dentrée est soit imprimée immédiatement (première occurrence), soit pas du tout (répétition).
  • Mais comment cela se compare-t-il à sort -u …?
  • @HashWizard Un simple sort -u change lordre.Ma réponse montre des solutions qui préservent lordre (lordre des premières occurrences, pour être précis).
  • @Gilles diriez-vous que cest plus rapide que trier -u pour les gros fichiers (10G) avec 50% de doublons ?

Réponse

sort -u big-csv-file.csv > duplicates-removed.csv 

Notez que le fichier de sortie sera être triés.

Commentaires

  • Pas aussi rapide que la commande awk dans dautres réponses, mais conceptuellement simple!
  • @Johann Je fais cela assez souvent sur des fichiers contenant des centaines de milliers (voire des millions) de courtes chaînes terminées par une nouvelle ligne. Jobtiens les résultats assez rapidement pour les expériences que je fais. Il peut être plus important sil est utilisé dans des scripts qui sont exécutés encore et encore, le gain de temps peut être considérable.
  • Utilisez sort -u pour supprimer les doublons pendant le tri, plutôt quaprès. (Et économise la bande passante de la mémoire) en le dirigeant vers un autre programme). Cest seulement mieux que la version awk si vous voulez aussi que votre sortie soit triée. (LOP sur cette question veut que sa commande dorigine soit préservée , cest donc une bonne réponse pour un cas dutilisation légèrement différent.)
  • Il a fallu environ une minute, pour moi, pour un fichier de 5,5 millions de lignes (1,8 Go au total). Génial.

Réponse

En supposant que vous puissiez 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 + de surcharge), vous pouvez le faire très facilement avec Perl.

$ perl -ne "print unless $dup{$_}++;" input_file > output_file 

Ceci préserve également lordre.

Vous pouvez extraire le nombre doccurrences de chaque ligne du hachage %dup si vous le souhaitez, comme un 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 le dup variable):

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

Commentaires

  • Cest trop bien @Mat, je était sur le point de slurp le fichier, lol ;-).
  • Maintenant en attente de @ManAtWork pour son tissage magique sed et awk aussi 🙂
  • génial encore pour le conseil awk: – )
  • Est-il possible de changer le script perl pour supprimer uniquement Les lignes adjacentes sont-elles en double?
  • @dumbledad: uniq fait cela tout seul

Réponse

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

gawk -i inplace "!a[$0]++" file 

Commentaires

  • Cela préserve-t-il lordre? Au fait, cela na pas fonctionné pour moi. Ma version est: GNU Awk 4.0.2
  • @Leonid oui, cest le cas. Il imprime la première occurrence dune ligne unique. Le support en place a été introduit pour la première fois dans la version 4.1, qui a été publiée en 2013.
  • Cela devrait être la réponse. Il ‘ supprime en fait la chaîne dupliquée dans le fichier existant ou actuel où la première réponse et la plupart des réponses ici nimpriment que les chaînes uniq / dupliquées et ne font rien et nous devons créer une autre sortie pour stocker le résultat.

Réponse

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

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

Commentaires

  • Lorsque vous donnez une réponse, il est préférable de donner quelques explications sur pourquoi votre réponse est la bonne. Alors, en quoi cette réponse diffère-t-elle de plusieurs des réponses précédentes?
  • De la page de manuel uniq: Remarque: 'uniq' does not detect repeated lines unless they are adjacent. Vous devez donc dabord la trier et la perdre lordre des lignes non dupliquées.

Réponse

Doublures Python One:

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

Commentaires

  • cela provoque le transfert du fichier entier dans la mémoire et peut ne pas convenir au problème de lOP ‘. Il nest pas non plus garanti de conserver lordre
  • Merci pour la suggestion, ‘ viens dapprendre Python .. je viens dessayer ceci à des fins dapprentissage ..:)
  • Ici ‘ s une version de Python 2.7 qui nest pas une seule ligne mais (succinctement) renvoie des lignes uniques préservant lordre sans charger le fichier entier en mémoire ni créer une seule chaîne gigantesque à alimenter pour imprimer
  • Merci @ 1_CR Jai quelque chose à apprendre aujourdhui 🙂 OrderedDict

Réponse

Aucune des réponses ici na fonctionné pour moi sur mon Mac, jai donc écrit un simple python script qui fonctionne pour moi. Jignore les espaces de début / fin et je 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 comme unique.py et exécutez comme ceci:

python unique.py inputfile.txt outputfile.txt 

Answer

SOLUTION SANS MAINTENIR LORDRE DE SÉQUENCE ORIGINALE

Je lai fait avec le morceau de code suivant.

sort duplicates.txt | uniq > noDuplicates.txt 

La commande sort trie les lignes par ordre alphabétique et la commande uniq supprime les doublons.

REMARQUE: La raison pour laquelle nous avons trié les lignes en premier est que uniq ne détecte pas les lignes en double sauf si elles sont adjacentes.

Commentaires

  • La question demande une méthode (de préférence ) qui maintient lordre dentrée; pourriez-vous modifier votre réponse pour résoudre ce problème? Notez quil existe des réponses existantes utilisant sort qui maintiennent lordre dentrée, et une réponse en utilisant sort sans maintenir lordre de saisie mais dune manière plus efficace que le redirection vers uniq.
  • @StephenKitt Modifié. Jai inspecté dautres réponses, mais je nai pu ‘ rien trouver uniquement avec les commandes de base. Merci pour vos commentaires.
  • Je vous ai donné un lien vers une réponse avec uniquement des commandes de base, en fait une seule commande, sort -u (qui fait partie de POSIX ) ;-).
  • @StephenKitt Jai vu cette réponse. Le mien est aussi un moyen de gérer le problème. Que veux-tu que je fasse de plus? Dois-je supprimer la réponse?
  • Non, ne supprimez pas votre réponse; Je voulais juste massurer que vous étiez au courant de lautre réponse, étant donné que vous avez dit que vous « ne pouviez ‘ rien trouver quavec des commandes de base ».

Réponse

Avec bash 4, une solution pure-bash qui tire parti des tableaux associatifs peut être utilisé. Voici un exemple

unset llist; declare -A llist; while read -r line; do if [[ ${llist[$line]} ]]; then continue else printf "%s\n" "$line" llist[$line]="x" fi done < file.txt 

Commentaires

  • Don ‘ t utiliser les boucles read pour traiter de gros fichiers texte. bash doit lire un octet à la fois pour éviter de dépasser une nouvelle ligne. Bash nest pas non plus très rapide dans le traitement de texte en général par rapport à awk. Si vous lutilisez, read -ra évitera de manger des contre-obliques dans votre entrée. Aussi, ‘ t oublier de unset llist après la boucle, si vous mettez ceci dans une fonction shell ou utilisez-le de manière interactive.
  • @PeterCordes, ou vous auriez pu simplement référencer ceci 🙂

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *