confronta due colonne di file diversi e stampa se corrisponde


16

Sto usando Solaris 10 e quindi le opzioni grep che coinvolgono -f non funzionano.

Ho due file separati da pipe:

file1:

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

file 2:

abc|123|
kumar|pki|
cab|234

Vorrei confrontare le prime due colonne di file2 con file1 (cercare in tutto il contenuto di file1 nelle prime due colonne) se corrispondono stampare la riga corrispondente di file1. Quindi cerca la seconda riga del file 2 e così via.

Uscita prevista:

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

I file che ho sono enormi, contenenti circa 400.000 righe, quindi vorrei velocizzare l'esecuzione.


Ho rimosso gli spazi iniziali dai tuoi esempi, se lo desideri, ripristina la modifica. Ricorda che gli spazi sono significativi, dovresti averli solo se esistono nei tuoi file reali.
terdon

Prova a utilizzare la versione GNU di grep, è sotto /usr/sfw/bin/ggrep. stackoverflow.com/questions/15259882/…
slm

Risposte:


21

Questo è ciò per cui awk è stato progettato per:

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

Spiegazione

  • -F'|' : imposta il separatore di campo su | .
  • NR==FNR: NR è il numero della riga di input corrente e FNR il numero di riga del file corrente. I due saranno uguali solo durante la lettura del primo file.
  • c[$1$2]++; next : se questo è il primo file, salva i primi due campi nel file c nell'array. Quindi, passa alla riga successiva in modo che questo venga applicato solo al 1 ° file.

  • c[$1$2]>0: il blocco else verrà eseguito solo se questo è il secondo file, quindi controlliamo se i campi 1 e 2 di questo file sono già stati visti ( c[$1$2]>0) e se lo sono stati, stampiamo la riga. In awk, l'azione predefinita è stampare la linea, quindi sec[$1$2]>0 è vera, la linea verrà stampata.


In alternativa, poiché hai taggato con Perl:

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

Spiegazione

La prima riga si aprirà file2, leggerà tutto fino al 2 ° |( .+?\|[^|]+) e lo salverà (il$& è il risultato dell'ultimo operatore di match) %knell'hash.

La seconda riga elabora file1, utilizza lo stesso regex per estrarre le prime due colonne e stampare la linea se tali colonne sono definite %knell'hash.


Entrambi gli approcci precedenti dovranno contenere in memoria le prime 2 colonne di file2. Questo non dovrebbe essere un problema se hai solo poche centinaia di migliaia di righe, ma se lo è, potresti fare qualcosa del genere

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

Ma sarà più lento.


Ma questo non caricherà tutto (le prime due colonne) di file2in memoria?
Joseph R.,

@terdon: awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0'è la versione più breve.
cuonglm,

non funziona ..
user68365

@ user68365: file2ha righe duplicate?
cuonglm,

NO non ha righe duplicate
user68365

1

penso

grep -Ff file2 file1

è quello che stai cercando. Dovrebbe essere efficiente, ma non sono sicuro che sarà accurato come vuoi. Se abc|123(ad esempio) viene trovato in una riga in file1colonne diverse, verrà stampata anche quella riga. Se puoi garantire che ciò non accadrà mai, la riga sopra dovrebbe funzionare.


Grep non sarebbe sufficiente, poiché abc | 123 potrebbe essere presente da qualche parte nel file. Inoltre sto usando Solaris 10 e non sono in grado di usare anche quell'opzione grep.
user68365

2
@ user68365 chiarisci tutto ciò nella tua domanda. Devi comunicarci il tuo sistema operativo e specificare che desideri abbinare solo le prime 2 colonne.
terdon

1

Se vuoi pensare al problema in modo simile a SQL, allora dovresti assolutamente provare uno strumento chiamato ' q ':

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

È più chiaro e facile da capire se si ha familiarità con la query SQL.


Grazie per una delle soluzioni meno criptiche, di gran lunga. È quello che voglio. Ma ho avuto qualche problema a trovare questo "strumento q"
Rolf,

Strumento molto utile.
ghilesZ

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

1
Come ho modificato e menzionato nella domanda, le opzioni grep -f non funzionano nel mio sistema
user68365

Solaris 10 ha un gnu core-utils in / usr / sfw / bin Usa / usr / sfw / bin / sed e / usr / sfw / bin / grep
mr_tron
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.