web-dev-qa-db-fra.com

extraire des valeurs uniques entre 2 ensembles / fichiers

En travaillant sous Linux/Shell env, comment puis-je accomplir les tâches suivantes:

le fichier texte 1 contient:

1
2
3
4
5

le fichier texte 2 contient:

6
7
1
2
3
4

J'ai besoin d'extraire les entrées du fichier 2 qui ne sont pas dans le fichier 1. Donc, "6" et "7" dans cet exemple.

Comment faire cela depuis la ligne de commande?

merci beaucoup!

24
mark
$ awk 'FNR==NR {a[$0]++; next} !a[$0]' file1 file2
6
7

Explication du fonctionnement du code:

  • Si nous travaillons sur le fichier 1, suivez chaque ligne de texte que nous voyons.
  • Si nous travaillons sur file2 et que nous n'avons pas vu le texte de la ligne, imprimez-le.

Explication des détails:

  • FNR est le numéro d'enregistrement du fichier en cours
  • NR est le numéro d'enregistrement global actuel de tous les fichiers d'entrée
  • FNR==NR n'est vrai que lorsque nous lisons le fichier1
  • $0 est la ligne de texte actuelle
  • a[$0] est un hachage avec la clé définie sur la ligne de texte actuelle
  • a[$0]++ suit que nous avons vu la ligne de texte actuelle
  • !a[$0] n'est vrai que lorsque nous n'avons pas vu le texte de la ligne
  • Imprimer la ligne de texte si le modèle ci-dessus renvoie vrai, c'est le comportement awk par défaut lorsqu'aucune action explicite n'est donnée
54
SiegeX

Utilisation de certains utilitaires moins connus:

sort file1 > file1.sorted
sort file2 > file2.sorted
comm -1 -3 file1.sorted file2.sorted

Cela produira des doublons, donc s'il y a 1 3 dans file1, mais 2 dans file2, cela affichera toujours 1 3. Si ce n'est pas ce que vous voulez, dirigez la sortie de sort à uniq avant de l'écrire dans un fichier:

sort file1 | uniq > file1.sorted
sort file2 | uniq > file2.sorted
comm -1 -3 file1.sorted file2.sorted

Il y a beaucoup d'utilitaires dans le paquet coreutils GNU qui permettent toutes sortes de manipulations de texte.

16
Daniel Gallagher

Je me demandais laquelle des solutions suivantes était la plus "rapide" pour les fichiers "plus gros":

awk 'FNR==NR{a[$0]++}FNR!=NR && !a[$0]{print}' file1 file2 # awk1 by SiegeX
awk 'FNR==NR{a[$0]++;next}!($0 in a)' file1 file2          # awk2 by ghostdog74
comm -13 <(sort file1) <(sort file2)
join -v 2 <(sort file1) <(sort file2)
grep -v -F -x -f file1 file2

Résultats de mes benchmarks en bref:

  • Ne pas utiliser grep -Fxf, c'est beaucoup plus lent (2 à 4 fois dans mes tests).
  • comm est légèrement plus rapide que join.
  • Si file1 et file2 sont déjà triés, comm et join sont beaucoup plus rapides que awk1 + awk2. (Bien sûr, ils n'assument pas de fichiers triés.)
  • awk1 + awk2, soi-disant, utilisent plus RAM et moins de CPU. Les temps d'exécution réels sont inférieurs pour comm probablement en raison du fait qu'il utilise plus de threads. Les temps de CPU sont inférieurs pour awk1 + awk2.

Par souci de concision, j'omets tous les détails. Cependant, je suppose que toute personne intéressée peut me contacter ou simplement répéter les tests. En gros, la configuration était

# Debian Squeeze, Bash 4.1.5, LC_ALL=C, slow 4 core CPU
$ wc file1 file2
  321599   321599  8098710 file1
  321603   321603  8098794 file2

Résultats typiques des courses les plus rapides

awk2: real 0m1.145s  user 0m1.088s  sys 0m0.056s  user+sys 1.144
awk1: real 0m1.369s  user 0m1.324s  sys 0m0.044s  user+sys 1.368
comm: real 0m0.980s  user 0m1.608s  sys 0m0.184s  user+sys 1.792
join: real 0m1.080s  user 0m1.756s  sys 0m0.140s  user+sys 1.896
grep: real 0m4.005s  user 0m3.844s  sys 0m0.160s  user+sys 4.004

BTW, pour les awkies: Il semble que a[$0]=1 est plus rapide que a[$0]++, et (!($0 in a)) est plus rapide que (!a[$0]). Donc, pour une solution awk, je suggère:

awk 'FNR==NR{a[$0]=1;next}!($0 in a)' file1 file2
8
xebeche

avec grep:

grep -F -x -v -f file_1 file_2 
5
sid_com

Que diriez-vous:

diff file_1 file_2 | grep '^>' | cut -c 3-

Cela imprimerait les entrées du fichier_2 qui ne se trouvent pas dans le fichier_1. Pour le résultat inverse, il suffit de remplacer ">" par "<". 'cut' supprime les deux premiers caractères ajoutés par 'diff', qui ne font pas partie du contenu original.

Les fichiers n'ont même pas besoin d'être triés.

5
Ivo

voici une autre solution awk

$ awk 'FNR==NR{a[$0]++;next}(!($0 in a))' file1 file2
6
7
3
ghostdog74

Si vous êtes vraiment déterminé à le faire à partir de la ligne de commande, ce site (recherchez "aucun doublon trouvé") a un exemple awk qui recherche les doublons. C'est peut-être un bon point de départ pour examiner cela.

Cependant, je vous encourage à utiliser Perl ou Python pour cela. Fondamentalement, le flux du programme serait:

findUniqueValues(file1, file2){
    contents1 = array of values from file1
    contents2 = array of values from file2
    foreach(value2 in contents2){
        found=false
        foreach(value1 in contents1){
            if (value2 == value1) found=true
        }
        if(!found) print value2
    }
}

Ce n'est pas la façon la plus élégante de le faire, car elle a une complexité temporelle O (n ^ 2), mais elle fera l'affaire.

0
David Weiser