web-dev-qa-db-fra.com

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

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.

16
user68365

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 dans le tableau c. 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 est d'imprimer la ligne donc si c[$1$2]>0 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 ouvrira file2, tout lire jusqu'au 2 | (.+?\|[^|]+) et enregistrez cela (le $& est le résultat du dernier opérateur de correspondance) dans le %k hachage.

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 %k hachage.


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 pouvez faire quelque chose comme

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

Mais ce sera plus lent.

21
terdon

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.

1
Vincent

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 sur une ligne dans file1 dans différentes colonnes, cette ligne sera également imprimée. Si vous pouvez garantir que cela ne se produira jamais, la ligne ci-dessus devrait fonctionner.

1
Joseph R.
$  sed 's/^/\^/' 2.txt > temp.txt ; grep 1.txt -f temp.txt
abc|123|BNY|Apple|
cab|234|cyx|orange|
0
mr_tron