J'ai deux fichiers:
abc.txt
abcd
xyz
pqrs
mno.txt
zzon
mkno
abcd
Comment puis-je faire ceci ?
Si votre objectif est de trouver des lignes communes ou peu communes, comm
serait ma commande incontournable ici.
Il compare deux fichiers et affiche, sur trois colonnes, des lignes propres au fichier 1, des lignes uniques au fichier 2 et des lignes figurant dans les deux fichiers, respectivement. Vous pouvez lui passer des drapeaux pour supprimer tout de cette sortie aussi. Par exemple, comm -1 file1 file2
supprimera la première colonne, les éléments propres à file1. comm -12 file1 file2
n'affichera que les éléments des deux fichiers.
Il y a une grosse mise en garde: l'entrée doit être triée. Nous pouvons travailler autour de cela.
Cela vous montrera tout ce qui est dans abc qui n'est pas dans mno:
comm -23 <(sort abc.txt) <(sort mno.txt)
Et vous pouvez diriger cela dans wc -l
pour obtenir un compte.
La raison pour laquelle j’utilise la méthode comm
est qu’une fois les fichiers triés, la comparaison côte à côte est très simple sur le plan des calculs. Si vous avez affaire à des millions d'entre eux, cela fera une différence.
Cela peut être démontré avec quelques fichiers simulés. J'ai un ordinateur assez rapide donc pour montrer la différence entre les approches, j'ai besoin d'un assez grand nombre d'échantillons. J'ai passé à 10 millions de chaînes de 10 caractères par fichier.
$ cat /dev/urandom | tr -dc '0-9' | fold -w 10 | head -10000000 > abc.txt
$ cat /dev/urandom | tr -dc '0-9' | fold -w 10 | head -10000000 > mno.txt
$ time comm -23 <(sort abc.txt) <(sort mno.txt) | wc -l
... 0m10.653s
$ time grep -Fcxv -f abc.txt mno.txt
... 0m23.920s
$ time grep -Fcwv -f abc.txt mno.txt
... 0m40.313s
$ time awk 'NR==FNR{a[$0]++};NR!=FNR && a[$0]' abc.txt mno.txt | wc -l
... 0m12.161s
Le tri est ce qui prend le plus de temps dans le mien. Si nous prétendons que abc.txt est statique, nous pouvons le pré-trier et cela rend les comparaisons futures beaucoup plus rapides:
$ sort abc.txt abc-sorted.txt
$ time comm -23 abc-sorted.txt <(sort mno.txt) | wc -l
... 0m7.426s
Vous pouvez les regarder et considérer quelques secondes comme non pertinentes, mais je dois souligner que celles-ci tournent sur une machine haut de gamme. Si vous vouliez faire cela sur un Raspberry Pi 3 (par exemple), vous constaterez des délais beaucoup plus lents et la différence augmentera à un point où il importe réellement.
pour obtenir une liste:
grep -Fwf abc.txt mno.txt
cela vous donne quelque chose de similaire à:
abcd
abcd
zef
si vous voulez juste obtenir une liste unique, utilisez-la comme ceci:
grep -Fwf abc.txt mno.txt | sort | uniq
et pour obtenir les comptes:
grep -Fcwv -f abc.txt mno.txt
-F
signifie: interprétez PATTERN comme une liste de chaînes fixes au lieu d'expressions régulières.-f
obtient des fichiers du fichier qui sera abc.txt
.mno.txt
pour des motifs-c
Compter le nombre de correspondances-w
Ne recherchez que les "mots entiers": la sous-chaîne correspondante doit être au début de la ligne ou précédée d'un caractère non-Word. De même, il doit être placé à la fin de la ligne ou suivi d'un caractère constitutif autre que Word. Les caractères constituant le mot sont des lettres, des chiffres et le trait de soulignement.-v
Inverser la rechercheNous pourrions utiliser awk pour faire le travail en transmettant deux fichiers, le fichier de modèle, puis le fichier à vérifier. Lorsque nous lisons le premier fichier, nous savons que NR==FNR
et à ce moment-là, nous pouvons lire des lignes dans un tableau. Quand NR!=FNR
nous vérifions si le tableau pour une telle ligne est défini.
$ cat abc.txt
abcd
xyz
pqrs
$ cat mno.txt
zzon
xyz
mkno
abcd
$ awk 'NR==FNR{a[$0]++};NR!=FNR && a[$0]' abc.txt mno.txt
xyz
abcd
Inversement, nous pouvons nier le motif pour imprimer les lignes qui ne sont pas dans abc.txt
$ awk 'NR==FNR{a[$0]++};NR!=FNR && ! a[$0]' abc.txt mno.txt
zzon
mkno
Et si nous voulons imprimer le nombre de ceux que nous pouvons utiliser sort
et wc
:
$ awk 'NR==FNR{a[$0]++};NR!=FNR && ! a[$0]' abc.txt mno.txt | sort -u | wc -l
2
Si l'une des listes de mots n'est pas triée, il serait plus rapide d'utiliser une structure de données d'ensemble efficace pour mémoriser les mots communs.
#!/usr/bin/env python3
import sys
with open(sys.argv[1]) as minuend_file:
minuend = frozenset(map(str.rstrip, minuend_file))
with open(sys.argv[2]) as subtrahend_file:
subtrahend = frozenset(map(str.rstrip, subtrahend_file))
difference = minuend - subtrahend
#print(*difference, sep='\n') # This prints the content of the set difference
print(len(difference)) # This prints the magnitude of the set difference
Usage:
python3 set-difference.py abc.txt mno.txt
Si vous voulez économiser un peu de mémoire pour le stockage intermédiaire et l'exécution, vous pouvez utiliser ce programme un peu plus difficile à comprendre:
#!/usr/bin/env python3
import sys
with open(sys.argv[1]) as minuend_file:
minuend = set(map(str.rstrip, minuend_file))
with open(sys.argv[2]) as subtrahend_file:
subtrahend = map(str.rstrip, subtrahend_file)
minuend.difference_update(subtrahend)
difference = minuend
del minuend
#print(*difference, sep='\n') # This prints the content of the set difference
print(len(difference)) # This prints the magnitude of the set difference
Étant donné abc.txt
et mno.txt
avec 1 mio de lignes non triées de 10 caractères aléatoires ASCII (voir la réponse de Oli pour la configuration):
$ time python3 set-difference.py abc.txt mno.txt
user 0m10.453s
vs.
$ export LC_COLLATE=C
$ time sort abc.txt > abc_sorted.txt
user 0m10.652s
$ time sort mno.txt > mno_sorted.txt
user 0m10.767s
$ time comm -23 abc_sorted.txt mno_sorted.txt | wc -l
9989882
user 0m1.600s
total: 23 secondes