Commande Unix pour trouver des lignes communes dans deux fichiers


184

Je suis sûr que j'ai trouvé une fois une commande unix qui pourrait imprimer les lignes communes de deux fichiers ou plus, est-ce que quelqu'un connaît son nom? C'était beaucoup plus simple que diff.


5
Les réponses à cette question ne sont pas nécessairement celles que tout le monde voudra, car elles commnécessitent des fichiers d'entrée triés. Si vous voulez juste du commun ligne par ligne, c'est génial. Mais si vous voulez ce que j'appellerais "anti-diff", commcela ne fait pas l'affaire.
Robert P. Goldman

@ RobertP.Goldman existe-t-il un moyen de se mettre en commun entre deux fichiers lorsque file1 contient un modèle partiel comme pr-123-xy-45et fichier2 contient ec11_orop_pr-123-xy-45.gz. J'ai besoin d'un fichier3 contenantec11_orop_pr-123-xy-45.gz
Chandan Choudhury

Voir ceci pour trier les fichiers texte ligne par ligne
y2k-shubham

Réponses:


222

La commande que vous recherchez est comm. par exemple:-

comm -12 1.sorted.txt 2.sorted.txt

Ici:

-1 : supprimer la colonne 1 (lignes uniques à 1.sorted.txt)

-2 : supprimer la colonne 2 (lignes uniques à 2.sorted.txt)


27
Utilisation typique: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK

46
Alors que comm a besoin de fichiers triés, vous pouvez utiliser grep -f fichier1 fichier2 pour obtenir les lignes communes des deux fichiers.
ferdy

2
@ferdy (répétant mon commentaire de votre réponse, car le vôtre est essentiellement une réponse répétée publiée sous forme de commentaire) grepfait des choses étranges auxquelles vous ne vous attendez peut-être pas. Plus précisément, tout dans 1.txtsera interprété comme une expression régulière et non comme une chaîne simple. En outre, toute ligne vide correspondra à 1.txttoutes les lignes de 2.txt. Cela grepne fonctionnera donc que dans des situations très spécifiques. Vous voudriez au moins utiliser fgrep(ou grep -f) mais la ligne blanche va probablement faire des ravages sur ce processus.
Christopher Schultz

11
Voir la réponse de ferdy ci-dessous, et celle de Christopher Schultz et mes commentaires à ce sujet. TL; DR - utilisation . grep -F -x -f file1 file2
Jonathan Leffler

1
@bapors: J'ai fourni un Q&A auto-répondu comme Comment obtenir la sortie de la commcommande dans 3 fichiers séparés? La réponse était beaucoup trop grande pour tenir confortablement ici.
Jonathan Leffler

63

Pour appliquer facilement la commande comm à des fichiers non triés , utilisez la substitution de processus de Bash :

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

Ainsi les fichiers abc et def ont une ligne en commun, celle avec "132". Utilisation de la communication sur des fichiers non triés:

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

La dernière ligne n'a produit aucune sortie, la ligne commune n'a pas été découverte.

Maintenant, utilisez comm sur les fichiers triés, en triant les fichiers avec la substitution de processus:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

Maintenant, nous avons la ligne 132!


2
alors ... sort abc > abc.sorted, sort dev > def.sortedpuis comm -12 abc.sorted def.sorted?
Nikana Reklawyks

1
@NikanaReklawyks Et n'oubliez pas de supprimer les fichiers temporaires par la suite, et de faire le ménage en cas d'erreur. Dans de nombreux scénarios, la substitution de processus sera également beaucoup plus rapide car vous pouvez éviter les E / S de disque tant que les résultats tiennent dans la mémoire.
tripleee

29

Pour compléter le one-liner Perl, voici son awkéquivalent:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

Cela lira toutes les lignes du file1tableau arr[], puis vérifiera chaque ligne file2si elle existe déjà dans le tableau (c'est-à-dire file1). Les lignes trouvées seront imprimées dans l'ordre dans lequel elles apparaissent file2. Notez que la comparaison in arrutilise la ligne entière de file2comme index au tableau, elle ne rapportera donc que les correspondances exactes sur des lignes entières.


2
CECI (!) Est la bonne réponse. Aucun des autres ne peut fonctionner en général (je n'ai pas essayé perlceux - là, parce que). Merci un million, Mme
entonio

1
Préserver l'ordre lors de l'affichage des lignes communes peut être très utile dans certains cas qui excluraient la communication à cause de cela.
tuxayo

1
Si quelqu'un veut faire la même chose en fonction d'une certaine colonne mais ne sait pas awk, remplacez simplement les deux $ 0 par $ 5 par exemple pour la colonne 5 afin d'obtenir des lignes partagées dans 2 fichiers avec les mêmes mots dans la colonne 5
FatihSarigol

24

Peut-être que tu veux dire comm?

Comparez les fichiers triés FILE1 et FILE2 ligne par ligne.

Sans options, produisez une sortie à trois colonnes. La première colonne contient des lignes uniques à FILE1, la deuxième colonne contient des lignes uniques à FILE2 et la troisième colonne contient des lignes communes aux deux fichiers.

Le secret pour trouver ces informations sont les pages d'informations. Pour les programmes GNU, ils sont beaucoup plus détaillés que leurs pages de manuel. Essayez info coreutilset il vous listera tous les petits outils utiles.


19

Tandis que

grep -v -f 1.txt 2.txt > 3.txt

vous donne les différences de deux fichiers (ce qui est en 2.txt et non en 1.txt), vous pouvez facilement faire un

grep -f 1.txt 2.txt > 3.txt

pour collecter toutes les lignes courantes, ce qui devrait fournir une solution simple à votre problème. Si vous avez trié des fichiers, vous devriez commquand même prendre . Cordialement!


2
grepfait des choses étranges auxquelles vous ne vous attendez peut-être pas. Plus précisément, tout dans 1.txtsera interprété comme une expression régulière et non comme une chaîne simple. En outre, toute ligne vide correspondra à 1.txttoutes les lignes de 2.txt. Cela ne fonctionnera donc que dans des situations très spécifiques.
Christopher Schultz

13
@ChristopherSchultz: Il est possible de mettre à niveau cette réponse pour qu'elle fonctionne mieux en utilisant les grepnotations POSIX , qui sont prises en charge par les grepvariantes d'Unix les plus modernes. Ajoutez -F(ou utilisez fgrep) pour supprimer les expressions régulières. Ajouter -x(pour exact) pour ne correspondre qu'à des lignes entières.
Jonathan Leffler

Pourquoi devrions-nous prendre commdes fichiers triés?
Ulysse BN

2
@UlysseBN commpeut travailler avec des fichiers arbitrairement volumineux tant qu'ils sont triés car il n'a besoin que de trois lignes en mémoire (je suppose que GNU commsaurait même ne garder qu'un préfixe si les lignes sont vraiment longues). La grepsolution doit conserver toutes les expressions de recherche en mémoire.
tripleee

9

Si les deux fichiers ne sont pas encore triés, vous pouvez utiliser:

comm -12 <(sort a.txt) <(sort b.txt)

et cela fonctionnera, en évitant le message d'erreur comm: file 2 is not in sorted order en faisant comm -12 a.txt b.txt.


Vous avez raison, mais il s'agit essentiellement de répéter une autre réponse , qui ne présente vraiment aucun avantage. Si vous décidez de répondre à une question plus ancienne qui a des réponses bien établies et correctes, l'ajout d'une nouvelle réponse tard dans la journée peut ne pas vous valoir de crédit. Si vous avez de nouvelles informations distinctives, ou si vous êtes convaincu que les autres réponses sont toutes fausses, ajoutez certainement une nouvelle réponse, mais `` encore une autre réponse '' donnant les mêmes informations de base longtemps après que la question a été posée est généralement gagnée vous gagnez beaucoup de crédit.
Jonathan Leffler

Je n'ai même pas vu cette réponse @JonathanLeffler car cette partie était à la toute fin de la réponse, mélangée avec d'autres éléments de réponse auparavant. Alors que l'autre réponse est plus précise, je pense que l'avantage du mien est que pour quelqu'un qui veut une solution rapide n'aura que 2 lignes à lire. Parfois, nous recherchons une réponse détaillée et parfois nous sommes pressés et une réponse prête à coller rapide à lire convient parfaitement.
Basj

Aussi je ne me soucie pas du crédit / représentant, je n'ai pas posté à cette fin.
Basj

1
Notez également que la syntaxe de substitution de processus <(command)n'est pas portable pour le shell POSIX, bien qu'elle fonctionne dans Bash et quelques autres.
tripleee

8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

cela fonctionne mieux que la commcommande comme il recherche chaque ligne de file1dans file2laquelle commne comparera si la ligne nen file1est égale à la ligne ndans file2.
teriiehina

1
@teriiehina: Non; commne compare pas simplement la ligne N dans fichier1 avec la ligne N dans fichier2. Il peut parfaitement gérer une série de lignes insérées dans l'un ou l'autre fichier (ce qui équivaut à supprimer une série de lignes de l'autre fichier, bien sûr). Cela nécessite simplement que les entrées soient triées.
Jonathan Leffler

Mieux que des commréponses si l'on veut garder l'ordre. Mieux vaut awkrépondre si l'on ne veut pas de doublons.
tuxayo



3

Sur une version limitée de Linux (comme un QNAP (nas) sur lequel je travaillais):

  • comm n'existait pas
  • grep -f file1 file2peut causer des problèmes comme le dit @ChristopherSchultz et l'utilisation grep -F -f file1 file2était vraiment lente (plus de 5 minutes - pas fini - plus de 2-3 secondes avec la méthode ci-dessous sur des fichiers de plus de 20 Mo)

Alors voici ce que j'ai fait:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

Si files.same.sorteddoit avoir été dans le même ordre que les originaux, alors ajoutez cette ligne pour le même ordre que file1:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

ou, pour le même ordre que file2:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same

2

À titre de référence, si quelqu'un cherche toujours à faire cela pour plusieurs fichiers, consultez la réponse liée à la recherche de lignes correspondantes dans de nombreux fichiers.


En combinant ces deux réponses ( ans1 et ans2 ), je pense que vous pouvez obtenir le résultat dont vous avez besoin sans trier les fichiers:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

Enregistrez-le simplement, donnez-lui les droits d'exécution ( chmod +x compareFiles.sh) et exécutez-le. Il prendra tous les fichiers présents dans le répertoire de travail courant et fera une comparaison tout contre tout en laissant dans le fichier "matching_lines" le résultat.

Choses à améliorer:

  • Ignorer les répertoires
  • Évitez de comparer tous les fichiers deux fois (fichier1 vs fichier2 et fichier2 vs fichier1).
  • Ajoutez peut-être le numéro de ligne à côté de la chaîne correspondante

-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

Cela devrait le faire.


1
Vous devriez probablement utiliser rm -f file3.txtsi vous allez supprimer le fichier; cela ne rapportera aucune erreur si le fichier n'existe pas. OTOH, ce ne serait pas nécessaire si votre script faisait simplement écho à la sortie standard, laissant l'utilisateur du script choisir où la sortie devrait aller. En fin de compte, vous voudrez probablement utiliser $1et $2(arguments de ligne de commande) au lieu de noms de fichiers fixes ( file1.outet file2.out). Cela laisse l'algorithme: ça va être lent. Il va lire file2.outune fois pour chaque ligne file1.out. Ce sera lent si les fichiers sont volumineux (disons plusieurs kilo-octets).
Jonathan Leffler

Bien que cela puisse théoriquement fonctionner si vous avez des entrées qui ne contiennent aucun métacaractère shell (indice: voyez les avertissements que vous obtenez de shellcheck.net ), cette approche naïve est terriblement inefficace. Un outil comme celui grep -Fqui lit un fichier en mémoire puis effectue un seul passage sur l'autre évite de boucler à plusieurs reprises sur les deux fichiers d'entrée.
tripleee
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.