comparer deux colonnes de fichiers différents et imprimer si cela correspond

16

J'utilise Solaris 10 et les options grep impliquant -f ne fonctionnent donc pas.

J'ai deux fichiers séparés par des tuyaux:

fichier1:

abc|123|BNY|apple|
cab|234|cyx|orange|
def|kumar|pki|bird|

fichier 2:

abc|123|
kumar|pki|
cab|234

Je voudrais comparer les deux premières colonnes de file2 avec file1 (rechercher dans tout le contenu de file1 dans les deux premières colonnes) si elles correspondent imprimer la ligne correspondante de file1. Recherchez ensuite la deuxième ligne du fichier 2 et ainsi de suite.

Production attendue:

abc|123|BNY|apple|
cab|234|cyx|orange|

Les fichiers que j'ai sont énormes, contenant environ 400 000 lignes, donc je voudrais rendre l'exécution rapide.

user68365
la source
J'ai supprimé les espaces de tête de vos exemples, si vous le souhaitez, veuillez annuler la modification. N'oubliez pas que les espaces sont importants, vous ne devriez les avoir que s'ils existent dans vos fichiers réels.
terdon
Essayez d'utiliser la version GNU de grep, c'est sous /usr/sfw/bin/ggrep. stackoverflow.com/questions/15259882/…
slm

Réponses:

21

C'est pour cela que awk a été conçu:

$ awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0' file2 file1
abc|123|BNY|apple|
cab|234|cyx|orange|

Explication

  • -F'|': définit le séparateur de champ sur |.
  • NR==FNR: NR est le numéro de ligne d'entrée actuel et FNR le numéro de ligne du fichier actuel. Les deux ne seront égaux que pendant la lecture du 1er fichier.
  • c[$1$2]++; next: s'il s'agit du 1er fichier, enregistrez les deux premiers champs du ctableau. Ensuite, passez à la ligne suivante pour que cela ne soit appliqué que sur le 1er fichier.

  • c[$1$2]>0: le bloc else ne sera exécuté que s'il s'agit du deuxième fichier donc nous vérifions si les champs 1 et 2 de ce fichier ont déjà été vus ( c[$1$2]>0) et s'ils l'ont été, nous imprimons la ligne. Dans awk, l'action par défaut consiste à imprimer la ligne. Si c[$1$2]>0c'est vrai, la ligne sera imprimée.


Alternativement, puisque vous avez tagué avec Perl:

perl -e 'open(A, "file2"); while(<A>){/.+?\|[^|]+/ && $k{$&}++};
         while(<>){/.+?\|[^|]+/ && do{print if defined($k{$&})}}' file1

Explication

La première ligne s'ouvrira file2, lira tout jusqu'au 2ème |( .+?\|[^|]+) et l'enregistrera ( $&le résultat du dernier opérateur de correspondance) dans le %khachage.

La deuxième ligne traite file1, utilise la même expression régulière pour extraire les deux premières colonnes et imprimer la ligne si ces colonnes sont définies dans le %khachage.


Les deux approches ci-dessus devront conserver les 2 premières colonnes du fichier 2 en mémoire. Cela ne devrait pas être un problème si vous n'avez que quelques centaines de milliers de lignes, mais si c'est le cas, vous pourriez faire quelque chose comme

cut -d'|' -f 1,2 file2 | while read pat; do grep "^$pat" file1; done

Mais ce sera plus lent.

terdon
la source
Mais cela ne chargera-t-il pas tous (les deux premières colonnes) de la file2mémoire?
Joseph R.
@terdon: awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0'est une version plus courte.
cuonglm
ça ne marche pas ..
user68365
@ user68365: Y a-t file2-il des lignes en double?
cuonglm
NON, il n'a pas de lignes en double
user68365
1

je pense

grep -Ff file2 file1

est ce que vous recherchez. Il devrait être efficace, mais je ne suis pas sûr qu'il sera aussi précis que vous le souhaitez. Si abc|123(par exemple) se trouve dans une ligne file1dans des colonnes différentes, cette ligne sera également imprimée. Si vous pouvez garantir que cela ne se produira jamais, la ligne ci-dessus devrait fonctionner.

Joseph R.
la source
Grep ne serait pas suffisant, car l'abc | 123 peut être présent quelque part dans le fichier. De plus, j'utilise Solaris 10 et je ne peux pas utiliser cette option grep aussi.
user68365
2
@ user68365 veuillez clarifier tout cela dans votre question. Vous devez nous indiquer votre système d'exploitation et spécifier que vous ne souhaitez faire correspondre que les 2 premières colonnes.
terdon
1

Si vous souhaitez penser le problème de manière similaire à SQL, alors vous devriez certainement essayer un outil nommé ' q ':

$ q -d '|' "select f1.* from file1 f1 join file2 f2 on (f1.c1 = f2.c1 and f1.c2 = f2.c2)"

Il est plus clair et plus facile à comprendre si vous connaissez la requête SQL.

Vincent
la source
Merci pour l'une des solutions les moins cryptiques, de loin. C'est ce que je veux. Mais j'ai eu du mal à trouver cet "outil q"
Rolf
Outil très utile.
ghilesZ
0
$  sed 's/^/\^/' 2.txt > temp.txt ; grep 1.txt -f temp.txt
abc|123|BNY|apple|
cab|234|cyx|orange|
mr_tron
la source
1
Comme j'ai édité et mentionné dans la question, les options grep -f ne fonctionnent pas sur mon système
user68365
Solaris 10 a un noyau-utils gnu dans / usr / sfw / bin Utilisez / usr / sfw / bin / sed et / usr / sfw / bin / grep
mr_tron