Komenda Uniksa, aby znaleźć linie wspólne w dwóch plikach

179

Jestem pewien, że kiedyś znalazłem polecenie uniksowe, które może drukować wspólne linie z dwóch lub więcej plików, czy ktoś zna jego nazwę? To było o wiele prostsze niż diff.

za dużo php
źródło
5
Odpowiedzi na to pytanie niekoniecznie będą potrzebne wszystkim, ponieważ commwymagają posortowanych plików wejściowych. Jeśli chcesz korzystać tylko z linii po linii, to świetnie. Ale jeśli chcesz czegoś, co nazwałbym „anti-diff”, commto nie działa.
Robert P. Goldman,
@ RobertP.Goldman jest sposobem na uzyskanie wspólnego między dwoma plikami, gdy plik1 zawiera częściowy wzorzec, podobnie jak pr-123-xy-45plik2 ec11_orop_pr-123-xy-45.gz. Potrzebuję plik3 zawierającyec11_orop_pr-123-xy-45.gz
Chandan Choudhury,
Zobacz to do sortowania plików tekstowych linia po linii
y2k-shubham

Odpowiedzi:

216

Polecenie, którego szukasz, to comm. na przykład:-

comm -12 1.sorted.txt 2.sorted.txt

Tutaj:

-1 : pomija kolumnę 1 (wiersze unikalne dla 1.sorted.txt)

-2 : pomija kolumnę 2 (wiersze unikalne dla 2.sorted.txt)

Jonathan Leffler
źródło
27
Typowe użycie: comm -12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK
45
Podczas gdy comm potrzebuje posortowanych plików, możesz wziąć grep -f plik1 plik2, aby uzyskać wspólne linie obu plików.
ferdy
2
@ferdy (Powtarzanie mojego komentarza z twojej odpowiedzi, ponieważ twój jest zasadniczo powtarzaną odpowiedzią opublikowaną jako komentarz) greprobi dziwne rzeczy, których możesz się nie spodziewać. W szczególności wszystko w 1.txtbędzie interpretowane jako wyrażenie regularne, a nie zwykły ciąg. Również każda pusta linia w 1.txtdopasuje wszystkie linie w 2.txt. Więc grepbędzie działać tylko w bardzo specyficznych sytuacjach. Przynajmniej chcesz użyć fgrep(lub grep -f), ale ta pusta linia prawdopodobnie spowoduje spustoszenie w tym procesie.
Christopher Schultz
11
Zobacz ferdy „s odpowiedź poniżej, a Christopher Schultz ” s i moje komentarze na jej temat. TL; DR - użycie grep -F -x -f file1 file2.
Jonathan Leffler,
1
@bapors: Dostarczyłem odpowiedzi na pytania, na które sam odpowiedziałeś, jak uzyskać dane wyjściowe commpolecenia w 3 osobnych plikach? Odpowiedź była o wiele za duża, by zmieścić się tutaj wygodnie.
Jonathan Leffler
62

Aby łatwo zastosować polecenie comm do nieposortowanych plików, użyj podstawienia procesu Basha :

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

Zatem pliki abc i def mają jedną wspólną linię, tę z „132”. Korzystanie z comm na nieposortowanych plikach:

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

Ostatni wiersz nie dał żadnych wyników, wspólny wiersz nie został wykryty.

Teraz użyj comm na posortowanych plikach, sortując pliki z podstawieniem procesu:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

Teraz mamy linię 132!

Stephan Wehner
źródło
2
tak ... sort abc > abc.sorted, sort dev > def.sorteda potem comm -12 abc.sorted def.sorted?
Nikana Reklawyks
1
@NikanaReklawyks A potem pamiętaj, aby usunąć pliki tymczasowe, a następnie poradzić sobie z czyszczeniem w przypadku błędu. W wielu scenariuszach podstawienie procesu będzie również znacznie szybsze, ponieważ można uniknąć wejścia / wyjścia dysku, o ile wyniki mieszczą się w pamięci.
tripleee
29

Aby uzupełnić jednowarstwową wersję Perla, oto jej awkodpowiednik:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

Spowoduje to odczytanie wszystkich wierszy z file1tablicy arr[], a następnie sprawdzenie każdej linii, file2jeśli już istnieje w tablicy (tj file1.). Znalezione wiersze zostaną wydrukowane w kolejności, w jakiej występują file2. Zauważ, że porównanie in arrużywa całej linii od file2indeksu do tablicy, więc będzie raportować tylko dokładne dopasowania dla całych linii.

Tatjana Heuser
źródło
2
TO (!) Jest poprawną odpowiedzią. Żadnego z pozostałych nie można ogólnie doprowadzić do działania (nie próbowałem perltych, ponieważ). Dzięki milionie, Pani
entonio
1
Zachowanie kolejności podczas wyświetlania wspólnych wierszy może być naprawdę przydatne w niektórych przypadkach, które wykluczałyby comm z tego powodu.
tuxayo,
1
Jeśli ktoś chce zrobić to samo w oparciu o określoną kolumnę, ale nie zna awk, po prostu zamień oba 0 $ na 5 $, na przykład dla kolumny 5, aby uzyskać linie dzielone w 2 plikach z tymi samymi słowami w kolumnie 5
FatihSarigol
24

Może masz na myśli comm?

Porównaj posortowane pliki PLIK1 i PLIK2 linia po linii.

Bez opcji utwórz wynik trójkolumnowy. Kolumna pierwsza zawiera wiersze unikalne dla PLIKU1, kolumna druga zawiera wiersze unikalne dla PLIKU2, a kolumna trzecia zawiera wiersze wspólne dla obu plików.

Sekretem w znalezieniu tych informacji są strony informacyjne. W przypadku programów GNU są one znacznie bardziej szczegółowe niż ich strony podręcznika. Spróbuj, info coreutilsa wyświetli się lista wszystkich małych przydatnych narzędzi.

Johannes Schaub - litb
źródło
19

Podczas

grep -v -f 1.txt 2.txt > 3.txt

daje różnice między dwoma plikami (co jest w 2.txt, a nie w 1.txt), możesz łatwo zrobić

grep -f 1.txt 2.txt > 3.txt

zebrać wszystkie typowe wiersze, które powinny zapewnić łatwe rozwiązanie problemu. Jeśli jednak posortowałeś pliki, powinieneś je wziąć comm. Pozdrowienia!

ferdy
źródło
2
greprobi dziwne rzeczy, których nie możesz się spodziewać. W szczególności wszystko w 1.txtbędzie interpretowane jako wyrażenie regularne, a nie zwykły ciąg. Również każda pusta linia w 1.txtdopasuje wszystkie linie w 2.txt. Będzie to działać tylko w bardzo specyficznych sytuacjach.
Christopher Schultz
13
@ChristopherSchultz: Możliwe jest uaktualnienie tej odpowiedzi, aby działała lepiej przy użyciu grepnotacji POSIX , które są obsługiwane przez grepznalezione w większości współczesnych wariantów Uniksa. Dodaj -F(lub użyj fgrep), aby ukryć wyrażenia regularne. Dodaj -x(dokładnie), aby dopasować tylko całe linie.
Jonathan Leffler,
Dlaczego powinniśmy brać commza posortowane pliki?
Ulysse BN
2
@UlysseBN commmoże pracować z dowolnie dużymi plikami, o ile są one posortowane, ponieważ zawsze muszą przechowywać tylko trzy linie w pamięci (domyślam się, commże GNU wiedziałby nawet, aby zachować tylko prefiks, jeśli linie są naprawdę długie). grepRozwiązanie wymaga, aby wszystkie wyrażenia wyszukiwania w pamięci.
tripleee
9

Jeśli dwa pliki nie są jeszcze posortowane, możesz użyć:

comm -12 <(sort a.txt) <(sort b.txt)

i to będzie działać, unikając komunikatu o błędzie comm: file 2 is not in sorted order , gdy robi comm -12 a.txt b.txt.

Basj
źródło
Masz rację, ale w zasadzie jest to powtórzenie innej odpowiedzi , która tak naprawdę nie przynosi żadnych korzyści. Jeśli zdecydujesz się odpowiedzieć na starsze pytanie, które ma dobrze ustalone i poprawne odpowiedzi, dodanie nowej odpowiedzi późno w ciągu dnia może nie przynieść ci uznania. Jeśli masz jakieś nowe, charakterystyczne informacje lub jesteś przekonany, że wszystkie inne odpowiedzi są błędne, dodaj nową odpowiedź, ale „jeszcze jedna odpowiedź”, podając te same podstawowe informacje długo po tym, jak pytanie zostało zadane, zwykle wygrywało ” zarobię ci dużo kredytu.
Jonathan Leffler
Nie widziałem nawet tej odpowiedzi @ JonathanLeffler, ponieważ ta część była na samym końcu odpowiedzi, zmieszana z innymi elementami odpowiedzi wcześniej. Podczas gdy druga odpowiedź jest bardziej precyzyjna, moim zdaniem korzyścią jest to, że dla kogoś, kto chce szybkiego rozwiązania, będą mieć tylko 2 wiersze do przeczytania. Czasami szukamy szczegółowej odpowiedzi, a czasem spieszymy się i szybka do przeczytania, gotowa do wklejenia odpowiedź jest w porządku.
Basj
Nie dbam też o kredyt / przedstawiciela, nie publikowałem tego w tym celu.
Basj
1
Zauważ też, że składnia podstawiania procesów <(command)nie jest przenośna dla powłoki POSIX, chociaż działa w Bash i niektórych innych.
potrójny
8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2
użytkownik2592005
źródło
to działa lepiej niż commpolecenia, gdyż przeszukuje każdy wiersz file1w file2którym commbędzie porównać tylko jeśli linia nw file1jest równa linii nw file2.
teriiehina
1
@teriiehina: Nie; commnie porównuje po prostu linii N w pliku 1 z linią N w pliku 2. Może doskonale zarządzać serią linii wstawionych do dowolnego pliku (co oczywiście jest równoznaczne z usunięciem serii linii z drugiego pliku). Wymaga jedynie posortowania danych wejściowych.
Jonathan Leffler
Lepsze niż commodpowiedzi, jeśli chce się zachować porządek. Lepsze niż awkodpowiedź, jeśli nie chce się duplikatów.
tuxayo,
Wyjaśnienie znajduje się tutaj: stackoverflow.com/questions/17552789/...
Chris Koknat
5
awk 'NR==FNR{a[$1]++;next} a[$1] ' file1 file2
RS John
źródło
3

W ograniczonej wersji Linuksa (jak QNAP (nas), nad którą pracowałem):

  • comm nie istniał
  • grep -f file1 file2może powodować pewne problemy, jak powiedział @ChristopherSchultz, a używanie grep -F -f file1 file2było naprawdę powolne (ponad 5 minut - nie skończyłem - ponad 2-3 sekundy z poniższą metodą na plikach powyżej 20 MB)

Oto co zrobiłem:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

Jeśli files.same.sortedpowinny być w tej samej kolejności niż oryginalne, dodaj ten wiersz dla tego samego zamówienia niż plik 1:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

lub, dla tego samego zamówienia, co plik 2:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same
Mistrz DJon
źródło
2

Tylko w celach informacyjnych, jeśli ktoś nadal zastanawia się, jak to zrobić dla wielu plików, zobacz połączoną odpowiedź na temat Znajdowanie pasujących wierszy w wielu plikach.


Łącząc te dwie odpowiedzi ( ans1 i ans2 ), myślę, że możesz uzyskać wynik, którego potrzebujesz, bez sortowania plików:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

Wystarczy go zapisać, nadać mu uprawnienia do wykonywania ( chmod +x compareFiles.sh) i uruchomić. Spowoduje to pobranie wszystkich plików znajdujących się w bieżącym katalogu roboczym i wykonanie porównania „wszystko przeciwko wszystkim”, pozostawiając wynik w pliku „dopasowywanie_ linii”.

Rzeczy do poprawy:

  • Pomiń katalogi
  • Unikaj porównywania wszystkich plików dwa razy (plik1 vs plik2 i plik2 vs plik1).
  • Może dodać numer wiersza obok pasującego łańcucha
akarpovsky
źródło
-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

To powinno to zrobić.

Alan Joseph
źródło
1
Prawdopodobnie powinieneś użyć, rm -f file3.txtjeśli chcesz usunąć plik; nie zgłosi żadnego błędu, jeśli plik nie istnieje. OTOH, nie byłoby konieczne, aby twój skrypt po prostu odbijał się echem od standardowego wyjścia, pozwalając użytkownikowi skryptu wybrać, dokąd dane wyjściowe powinny się udać. Ostatecznie prawdopodobnie będziesz chciał użyć $1i $2(argumenty wiersza poleceń) zamiast ustalonych nazw plików ( file1.outi file2.out). Pozostawia to algorytm: będzie on powolny. Przeczyta file2.outraz dla każdej linii file1.out. Będzie dużo, jeśli pliki będą duże (powiedzmy kilka kilobajtów).
Jonathan Leffler
Chociaż może to nominalnie zadziałać, jeśli masz dane wejściowe, które nie zawierają żadnych metaznaków powłoki (wskazówka: zobacz, jakie ostrzeżenia otrzymujesz z shellcheck.net ), to naiwne podejście jest strasznie nieskuteczne. Takie narzędzie, grep -Fktóre odczytuje jeden plik do pamięci, a następnie wykonuje jedno przejście przez drugi, pozwala uniknąć powtarzania się obu plików wejściowych.
tripleee,