2
votes

Utilisation d'AWK pour fusionner deux fichiers en fonction de plusieurs conditions

Je sais que cette question a déjà été posée plusieurs fois. Voici un exemple:

Utilisation d'AWK pour fusionner deux fichiers basés sur plusieurs colonnes

Mon objectif est d'imprimer les colonnes 2, 4, 5 et 7 de file_b et les colonnes 17 et 18 de file_a si la correspondance suivante se produit: Les colonnes 2, 6 et 7 de file_a.csv correspondent respectivement aux colonnes 2, 4 et 5 de file_b.csv.

Mais peu importe ce que j'essaye, je ne peux pas le faire fonctionner pour mon cas . Voici mes deux fichiers:

file_a.csv

col2, col4, col5, col7,
a, b, c, 4.5,
e, f, g, 6.3,

file_b.csv

awk -F, -v RS='\r\n' 'NR==FNR{key[$2 FS $6 FS $7]=$17 FS $18;next} {if($2 FS $4 FS $5 in key); print $2 FS $4 FS $5 FS $7 FS key[$2 FS $6 FS $7]}' file_a.csv file_b.csv > out.csv

Sortie devrait ressembler à ceci:

col2, col4, col5, col7, col17, col18
a, b, c, 4.5, 145, 88
e, f, g, 6.3, 101, 96

J'ai essayé ceci:

col2, col4, col5, col7
a, b, c, 4.5
e, f, g, 6.3
x, k, l, 12.9

Actuellement, le résultat que j'obtiens est: p >

col2, col6, col7, col17, col18
a, b, c, 145, 88
e, f, g, 101, 96
x, y, z, 243, 222

En d'autres termes, col17 et col18 de file_a n'apparaissent pas.

Hier, j'ai posé une question connexe où j'avais des problèmes avec les sauts de ligne. Cela a été résolu et résolu, mais maintenant je pense que ce problème est lié à la vérification de la condition if.

Mise à jour: Je partage des liens vers des copies tronquées des données réelles. La seule différence entre ces fichiers et les fichiers réels est que les vrais ont des millions de lignes. Ceux-ci n'en ont que 10 chacun.

file_a. csv

file_b.csv


2 commentaires

Avez-vous besoin de caractères \ r dans votre (vos) fichier (s) d'entrée? Sinon, débarrassez-vous-en d'abord en utilisant tr -d '\ r' temp && mv temp Input_file . Imprimez également awk '{print $ 17, $ 18}' Input_file pour voir que vous obtenez une sortie comme des désirs, laissez-nous knoe alors?


Merci @ RavinderSingh13. Voir mon commentaire sur la réponse de Tiw ci-dessous.


3 Réponses :


1
votes

Veuillez essayer ceci (GNU sed):

dos2unix file

C'est le moment où le bloc BEGIN entre en jeu. OFS entre également en jeu.
Lorsque nous imprimons de nombreux champs séparés par la même chose, nous pouvons définir OFS , et simplement mettre une virgule entre les éléments que nous voulons imprimer.

Il n'est pas nécessaire de vérifier clé dans arr lorsque vous avez attribué une valeur à une clé du tableau,
par défaut, lorsque arr [somekey] n'est pas affecté auparavant, il est vide / "" , et il est évalué à false dans awk ( 0 dans un contexte scalaire), et une chaîne non vide est évaluée à true (il n'y a pas littéralement de true et false dans awk ).
(Vous avez utilisé un nom de array incorrect, le $ 2, $ 6, $ 7 est la clé dans le tableau arr ici. Il est déroutant d'utiliser clé comme nom de tableau.)

Vous pouvez tester un concept simple comme celui-ci:

sed -i 's/\r//' files

Vous n'avez pas besoin d'un fichier d'entrée pour exécuter le bloc BEGIN .

De plus, vous pouvez parfois utiliser des guillemets, pour éviter toute confusion et problème sous-jacent.

Mise à jour : Vos fichiers se terminent en fait par \ n , si vous ne pouvez pas être sûr de la fin de la ligne, utilisez ceci:

awk 'BEGIN{RS="[\r\n]+";FS=OFS=",";SUBSEP=FS}NR==FNR{arr[$2,$6,$7]=$17 FS $18;next} {if(arr[$2,$4,$5]) print $2,$4,$5,$7,arr[$2,$4,$5]}' file_a.csv file_b.csv

ou ceci (This on ignorera les lignes vides):

awk 'BEGIN{RS="\r\n|\n|\r";FS=OFS=",";SUBSEP=FS}NR==FNR{arr[$2,$6,$7]=$17 FS $18;next} {if(arr[$2,$4,$5]) print $2,$4,$5,$7,arr[$2,$4,$5]}' file_a.csv file_b.csv

Aussi , il vaut mieux d'abord convertir pour éviter de telles situations, en:

awk 'BEGIN{print arr["newkey"]}'

Ou vous pouvez utiliser la commande dos2unix :

awk 'BEGIN{RS="\r\n";FS=OFS=",";SUBSEP=FS}NR==FNR{arr[$2,$6,$7]=$17 FS $18;next} {if(arr[$2,$4,$5]) print $2,$4,$5,$7,arr[$2,$4,$5]}'

C'est un outil de ligne de commande pratique pour faire ce qui précède uniquement.
Vous pouvez l'installer si vous ne l'avez pas encore dans votre système.
Une fois converti, vous n'avez pas besoin d'attribuer RS dans des situations normales.


6 commentaires

Ca ne fonctionne pas. Maintenant, j'obtiens un fichier vide en sortie. J'ai ajouté les noms de fichiers à la fin de votre code: file_a.csv file_b.csv> out.csv


J'ai ajouté des données réelles maintenant. Veuillez jeter un œil.


Fonctionne parfaitement maintenant. Mais je suis confus au sujet de \ r et \ n. Suite au commentaire de RavinderSingh13 ci-dessus, j'ai essayé de supprimer "CR" et "LF" (comme indiqué dans Notepad ++). J'ai réussi à supprimer CR mais pas LF. Ai-je raison de dire que CR = / n et LF = / r? Est-ce que / n = nouvelle ligne? Qu'est-ce que / r alors?


CR = \ r , LF = \ n . Différentes fins de ligne. Conventionnel: Windows: \ r \ n , linux: \ n , macOS: \ r (je ne suis pas sûr de celui-ci). Ce sont des caractères différents qui font la même chose aujourd'hui. (Dans l'ancien temps, le chariot signifie passer au début de la ligne, retourner signifie passer à la ligne suivante.) @Ahmed_m


Merci. Dernière question: que signifient [] et le + dans RS = "[\ r \ n] +"?


@ahmed_m [] signifie tout caractère qu'il contient (ou tout autre lorsque commence par ^ à l'intérieur). + signifie une ou plusieurs occurrences . Recherchez Regex pour en savoir plus.



0
votes
$ awk 'BEGIN      {RS="\r\n"; FS=OFS=","}
       NR==FNR    {a[$2,$6,$7]=$17 OFS $18; next} 
  ($2,$4,$5) in a {print $2,$4,$5,$7,a[$2,$4,$5]}' file1 file2 > output
Your main issue is, in the array lookup the index you should use is the second file key, not the first file key.  Also the semicolon after the if condition is wrong. The rest is cosmetics only.  Not sure you want the output \r\n terminated, if so set ORS=RS as well, otherwise it's newline only.

0 commentaires

0
votes

Puisque vous avez mentionné que le fichier est énorme, vous pouvez essayer Perl, si c'est une option.

Les fichiers sont supposés avoir "\ r".

$ cat file_a.csv
col2, col6, col7, col17, col18
a, b, c, 145, 88
e, f, g, 101, 96
x, y, z, 243, 222
$ cat file_b.csv
col2, col4, col5, col7
a, b, c, 4.5
e, f, g, 6.3
x, k, l, 12.9
$ perl -F, -lane 'BEGIN { %kv=map{chomp;chop;@a=split(",");"$a[0],$a[1],$a[2]"=>"$a[3]"} qx(cat file_b.csv) } if($.>1){ $x="$F[0],$F[1],$F[2]";chomp($F[-1]);print "$x,$kv{$x}",join(",",@F[-2,-1]) if $kv{$x} } ' file_a.csv
a, b, c, 4.5 145, 88
e, f, g, 6.3 101, 96
$


0 commentaires