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.

użytkownik68365
źródło
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.

terdon
źródło
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ć.

Joseph R.
źródło
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.

Vincent
źródło
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|
mr_tron
źródło
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