porównaj dwie kolumny różnych plików i wydrukuj, jeśli pasuje


16

Używam Solaris 10, więc opcje grep obejmujące -f nie działają.

Mam dwa pliki oddzielone potokami:

plik1:

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

plik 2:

abc|123|
kumar|pki|
cab|234

Chciałbym porównać dwie pierwsze kolumny pliku 2 z plikiem 1 (przeszukaj całą zawartość pliku 1 w pierwszych dwóch kolumnach), jeśli pasują, wydrukuj dopasowaną linię pliku 1. Następnie wyszukaj drugą linię pliku 2 i tak dalej.

Oczekiwany wynik:

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

Pliki, które mam, są ogromne i zawierają około 400 000 wierszy, dlatego chciałbym, aby wykonanie było szybkie.


Usunąłem wiodące spacje z twoich przykładów, jeśli chcesz, cofnij edycję. Pamiętaj, że spacje są znaczące, powinieneś je mieć tylko wtedy, gdy istnieją w twoich rzeczywistych plikach.
terdon

Spróbuj użyć wersji GNU grep, jest poniżej /usr/sfw/bin/ggrep. stackoverflow.com/questions/15259882/…
slm

Odpowiedzi:


21

Właśnie do tego przeznaczony jest awk:

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

Wyjaśnienie

  • -F'|': ustawia separator pól na |.
  • NR==FNR: NR jest bieżącym numerem linii wejściowej, a FNR numerem bieżącego pliku. Oba będą równe tylko podczas odczytywania pierwszego pliku.
  • c[$1$2]++; next: jeśli jest to pierwszy plik, zapisz pierwsze dwa pola w ctablicy. Następnie przejdź do następnego wiersza, aby zastosować go tylko do pierwszego pliku.

  • c[$1$2]>0: blok else zostanie wykonany tylko wtedy, gdy jest to drugi plik, więc sprawdzamy, czy pola 1 i 2 tego pliku były już widoczne ( c[$1$2]>0), a jeśli tak, to wypisujemy wiersz. W awkdomyślnym działaniem jest drukowanie linii, więc jeśli c[$1$2]>0jest prawdziwa, linia zostanie wydrukowany.


Alternatywnie, ponieważ otagowałeś Perl:

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

Wyjaśnienie

Pierwszy wiersz się otworzy file2, przeczyta wszystko do drugiego |( .+?\|[^|]+) i zapisze to ( $&jest wynikiem operatora ostatniego dopasowania) w %khaszu.

Druga linia przetwarza plik 1, używa tego samego wyrażenia regularnego do wyodrębnienia dwóch pierwszych kolumn i wydrukowania linii, jeśli kolumny te są zdefiniowane w %khaszowaniu.


Oba powyższe podejścia będą musiały przechowywać 2 pierwsze kolumny pliku2 w pamięci. Nie powinno to stanowić problemu, jeśli masz tylko kilkaset tysięcy linii, ale jeśli tak, możesz zrobić coś takiego

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

Ale to będzie wolniejsze.


Ale czy to nie załaduje wszystkich (pierwszych dwóch kolumn) file2do pamięci?
Joseph R.

@terdon: awk -F'|' 'NR==FNR{c[$1$2]++;next};c[$1$2] > 0'jest krótszą wersją.
cuonglm

to nie działa ..
user68365

@ user68365: Czy file2mają zduplikowane wiersze?
cuonglm

NIE, nie ma żadnych zduplikowanych wierszy
użytkownik68365

1

Myślę

grep -Ff file2 file1

jest tym, czego szukasz. Powinien być wydajny, ale nie jestem pewien, czy będzie tak dokładny, jak chcesz. Jeśli abc|123(na przykład) zostanie znaleziony w wierszu file1w różnych kolumnach, wiersz ten zostanie również wydrukowany. Jeśli możesz zagwarantować, że tak się nigdy nie stanie, powyższa linia powinna działać.


Grep nie byłby wystarczający, ponieważ abc | 123 może być obecny gdzieś w pliku. Ponadto używam solaris 10 i nie mogę również użyć tej opcji grep.
user68365

2
@ user68365 wyjaśnij to wszystko w swoim pytaniu. Musisz podać nam swój system operacyjny i określić, że chcesz dopasować tylko pierwsze 2 kolumny.
terdon

1

Jeśli chcesz myśleć o problemie w sposób podobny do SQL, zdecydowanie powinieneś wypróbować narzędzie o nazwie „ q ”:

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

Jest to bardziej jasne i łatwiejsze do zrozumienia, jeśli znasz kwerendę SQL.


Zdecydowanie dziękuję za jedno z najmniej zagadkowych rozwiązań. To jest to czego chce. Ale miałem problemy ze znalezieniem tego „narzędzia q”
Rolf

Bardzo przydatne narzędzie.
ghilesZ

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

1
Jak już edytowałem i wspomniałem w pytaniu, opcje grep -f nie działają w moim systemie
user68365

Solaris 10 ma podstawowe narzędzia GNU w / usr / sfw / bin Użyj / usr / sfw / bin / sed i / usr / sfw / bin / grep
mr_tron
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.