J'ai le fichier suivant:
ABC MNH 1 4 UHR LOI 2 8 QWE LOI 3 PUQ LOI 5
Je souhaite supprimer tous les doublons (sur la base des deux premières colonnes - par exemple, la ligne 6 est un double de la ligne 4). Je souhaite également fusionner les entrées où les colonnes 1 et 2 sont permutées (par exemple, les lignes 1 et 4). Cela signifie que cette liste devrait aboutir à:
ABC MNH 1 UHR LOI 2 QWE LOI 3 MNH ABC 4 PUQ LOI 5 MNH ABC 6 QWE LOI 7 LOI UHR 8
Cependant, ce fichier est énorme. Environ 2-3 To. Est-ce que cela peut être fait avec awk / sed?
3 Réponses :
Le GNU datmash toujours utile à la rescousse!
#!/bin/sh input="$1" sqlite3 -batch -noheader -list temp.db 2>/dev/null <<EOF .separator \t PRAGMA page_size = 8096; -- Make sure the database can grow big enough CREATE TABLE data(col1, col2, col3, PRIMARY KEY(col1, col2)) WITHOUT ROWID; .import "$input" data SELECT col1, col2, group_concat(col3, ' ') FROM ( SELECT col1, col2, col3 FROM data WHERE col1 < col2 UNION ALL SELECT col2, col1, col3 FROM data WHERE col2 < col1 ) GROUP BY col1, col2 ORDER BY col1, col2; EOF rm -f temp.db
MNH ABC 6
devient ABC MNH 6
), et génère des colonnes séparées par des tabulations (qui est ce avec quoi datamash
fonctionne par défaut). datamash
pour produire une seule ligne pour toutes les deux premières colonnes en double, avec une liste séparée par des virgules des valeurs des troisièmes colonnes comme troisième colonne de la sortie (comme ABC MNH 1,4
) La plupart des solutions économes en mémoire nécessiteront le tri des données, et bien que le programme sort
soit assez bon pour le faire, il utilisera toujours un tas de fichiers temporaires. vous aurez besoin d'environ 2-3 téraoctets d'espace disque libre.
Si vous comptez faire beaucoup de choses avec les mêmes données, il vaut probablement la peine de les trier une fois et de réutiliser ce fichier à la place de le trier à chaque fois comme première étape d'un pipeline:
#!/usr/bin/perl use warnings; use strict; use feature qw/say/; my %keys; while (<>) { chomp; my ($col1, $col2, $col3) = split ' '; $keys{$col1}{$col2} = $col3 unless exists $keys{$col1}{$col2}; } $, = " "; while (my ($col1, $sub) = each %keys) { while (my ($col2, $col3) = each %$sub) { next unless defined $col3; if ($col1 lt $col2 && exists $keys{$col2}{$col1}) { $col3 .= " $keys{$col2}{$col1}"; $keys{$col2}{$col1} = undef; } elsif ($col2 lt $col1 && exists $keys{$col2}{$col1}) { next; } say $col1, $col2, $col3; } }
S'il y a suffisamment de doublons et suffisamment de RAM pour qu'il soit possible de conserver les résultats en mémoire, il peut être effectué en un seul passage dans le fichier d'entrée en supprimant les doublons au fur et à mesure, puis en parcourant toutes les paires de valeurs restantes:
$ sort -k1,2 -u input.txt > unique_sorted.txt $ awk ... unique_sorted.txt | ...
Cela produit une sortie dans un ordre arbitraire non trié pour des raisons d'efficacité .
Et une approche utilisant sqlite (nécessite également beaucoup d'espace disque supplémentaire, et que les colonnes sont séparées par des tabulations, pas par des espaces arbitraires):
$ sort -k1,2 -u input.txt | awk -v OFS="\t" '$2 < $1 { tmp = $1; $1 = $2; $2 = tmp } { print $1, $2, $3 }' | sort -k1,2 | datamash groupby 1,2 collapse 3 | tr ',' ' ' ABC MNH 1 4 LOI PUQ 5 LOI QWE 3 LOI UHR 2 8
Si vos deux premières colonnes ne contiennent que 3 caractères maximum, vous aurez 26 ^ 6 combinaisons possibles pour les deux premières colonnes. C'est très facile à gérer avec awk.
{ key1=$1$2; key2=$2$1 } (key1 in b) || (key2 in b) { next } # permutation printed, is duplicate (key1 in a) { next } # only duplicate, no permutation found (key2 in a) { # permutation found print $2,$1,a[key2],$3 # - print delete a[key1] # - delete keys from a delete a[key2] b[key1] # - store key in b next # - skip the rest } { a[key1]=$3 } END { for (k in a) { print substr(1,3,k),substr(4,3,k),a[k] } }
Ceci n'imprimera cependant que les permutations, et comme demandé, 2 éléments maximum. En conséquence, le tableau a
aura à la fois key1
et la clé permutée key2
dans le tableau au cas où une permutation est trouvée, sinon il sera ont seulement key1
.
Ceci peut être nettoyé avec un deuxième tableau gardant une trace si une permutation est déjà imprimée. Appelez-le b
. De cette façon, vous pouvez éliminer 2 éléments de a
tout en gardant la trace d'un élément dans b
:
{ key1=$1$2; key2=$2$1 } (key1 in a) { next } # duplicate :> skip (key2 in a) { print $2,$1,a[key2],$3 } # permutation :> print { a[key1]=$3 } # store value
@EdMorton j'ai toujours pris 3 caractères. Je voulais enregistrer l'octet de sousp
@EdMorton Chaque entrée du tableau a
représente l'original key1
trouvé. Je teste si key1
est dans le tableau pour vérifier un doublon, mais si key2
est dans le tableau, nous avons rencontré une permutation. À la fin, vous devriez avoir à la fois key1
et key2
dans le tableau que vous pouvez utiliser pour d'autres doublons. Il existe un moyen de nettoyer la baie.
Je ne comprends pas pourquoi ce que vous avez publié est votre sortie attendue, vous devrez peut-être le masser, mais à mon humble avis, c'est la bonne façon d'aborder le problème de sorte que seul le "tri" gère le stockage de l'entrée multi-TB en interne ( et sort est conçu pour le faire avec la pagination, etc.) alors que les scripts awk ne traitent qu'une ligne à la fois et en gardent très peu en mémoire:
$ cat tst.sh #!/bin/env bash awk '{print ($1>$2 ? $1 OFS $2 : $2 OFS $1), $0}' "$1" | sort -s -k1,2 | awk ' { curr = $1 OFS $2 } prev != curr { if ( NR>1 ) { print rec } rec = $0 sub(/^([^[:space:]]+[[:space:]]+){2}/,"",rec) sub(/[[:space:]]+[^[:space:]]+$/,"",rec) delete seen prev = curr } !seen[$3,$4]++ { rec = rec OFS $NF } END { print rec } ' $ ./tst.sh file ABC MNH 1 4 PUQ LOI 5 QWE LOI 3 UHR LOI 2 8
Une implémentation alternative après discussion avec @kvantour dans les commentaires ci-dessous (nécessite le tri GNU pour le tri stable -s
):
$ cat tst.sh #!/bin/env bash awk '{print ($1>$2 ? $1 OFS $2 : $2 OFS $1), $0}' "$1" | sort -k1,2 | awk ' { curr = $1 OFS $2 } prev != curr { if ( NR>1 ) { print rec } rec = $0 sub(/^([^[:space:]]+[[:space:]]+){2}/,"",rec) prev = curr next } { rec = rec OFS $NF } END { print rec } ' $ ./tst.sh file ABC MNH 1 4 6 PUQ LOI 5 QWE LOI 3 7 LOI UHR 8 2
Je ne suis pas sûr, mais je ne pense pas que le premier tube et tri sera capable de traiter le fichier de 2 To.
De plus, dans votre exemple de sortie, le 6
de la première ligne ne doit pas apparaître car la combinaison de touches MNH ABC
était déjà vue précédemment avec la valeur 4
. cela implique également que la commande de tri pourrait changer l'ordre d'origine des clés en double, ce qui affectera la sortie.
Concernant le tube et le tri, quelques informations intéressantes ici: stackoverflow.com/questions/43362433/...
Je crois que quelque chose comme sort -s -T / chemin / vers / extra / harddisk -S4G
pourrait le faire. @riasc Si ce qui précède n'a pas fonctionné, veuillez nous en informer et nous essaierons de trouver une autre solution.
Qu'est-ce que tu as essayé jusque-là?
C'est un très gros fichier. Normalement, c'est amusant quand les gens disent qu'ils ont un gros fichier, et il s'avère qu'il fait 20 Mo et que nous sommes comme "pas de problème!". Quoi que vous essayiez, ou quoi que ce soit suggéré, je l'essayerais d'abord sur un morceau de 20 Mo du fichier, puis je multiplierais le temps par 150000 pour voir si c'est pratique. En outre, la manière typique de résoudre ce problème sur un fichier non trié en une seule passe consiste à charger la mémoire avec toutes les paires uniques indépendantes de l'ordre dans vos colonnes un et deux. Cela peut être pratique ou non selon le pourcentage de doublons.
Quelle est la longueur des chaînes de votre fichier réel. Sont-ils toujours 3 personnages? C'est juste pour connaître le nombre de combinaisons possibles. S'ils sont 3, alors vous n'avez que 26 ^ 6 combinaisons uniques possibles, donc il est gérable avec awk.
Attendre. Ces numéros à la fin de vos lignes sont-ils VRAIMENT présents dans vos données ou essayez-vous simplement de nous montrer les numéros de ligne d'entrée à travers l'entrée / sortie?