Porównanie dwóch plików w terminalu linux


168

Istnieją dwa pliki o nazwach „a.txt” i „b.txt”, oba zawierają listę słów. Teraz chcę sprawdzić, które słowa są dodatkowe w „a.txt”, a których nie ma w „b.txt” .

Potrzebuję wydajnego algorytmu, ponieważ muszę porównać dwa słowniki.


27
diff a.txt b.txtnie wystarcza?
ThanksForAllTheFish

Czy słowa mogą wystąpić kilka razy w każdym pliku? Czy możesz sortować pliki?
Basile Starynkevitch

Potrzebuję tylko tych słów, których nie ma w pliku „b.txt” i które są obecne w pliku a.txt
Ali Imran

Odpowiedzi:


343

jeśli masz zainstalowany vim, spróbuj tego:

vimdiff file1 file2

lub

vim -d file1 file2

uznasz to za fantastyczne.wprowadź opis obrazu tutaj


9
zdecydowanie niesamowity, dobry w projektowaniu i łatwy do wykrycia różnic. Ohmygod
Zen,

1
Twoja odpowiedź jest niesamowite, ale mój nauczyciel mi potrzebne, aby nie używać żadnych funkcji biblioteki: P
Ali Imran

1
Co za wspaniałe narzędzie! Jest to niezwykle pomocne.
user1205577

1
Jakie jest znaczenie tych kolorów?
zygimantus

1
Kolorowe kody oznaczają, że są różne w dwóch plikach. @zygimantus
Fengya Li,

73

Sortuj je i używaj comm:

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

commporównuje (posortowane) pliki wejściowe i domyślnie wyświetla trzy kolumny: wiersze, które są unikalne dla a, wiersze unikalne dla b i wiersze, które są obecne w obu. Określając -1, -2i / lub -3można stłumić odpowiednie wyjście. Dlatego comm -23 a bwyświetla tylko wpisy, które są unikalne dla. Używam <(...)składni do sortowania plików w locie, jeśli są już posortowane, nie potrzebujesz tego.


Dodałem własną odpowiedź, używając tylko poleceń grep, powiedz mi, czy jest bardziej wydajna?
Ali Imran

3
@AliImran commjest bardziej wydajny, ponieważ wykonuje zadanie w jednym przebiegu, bez przechowywania całego pliku w pamięci. Ponieważ używasz słowników, które najprawdopodobniej są już posortowane, nawet sortich nie potrzebujesz . Użycie grep -f file1 file2z drugiej strony załaduje całość file1do pamięci i porówna każdą linię file2ze wszystkimi tymi wpisami, co jest znacznie mniej wydajne. Jest to przydatne głównie w przypadku małych, nieposortowanych -f file1.
Anders Johansson

1
Dzięki @AndersJohansson za udostępnienie polecenia „comm”. To naprawdę fajne. Często muszę wykonywać zewnętrzne łączenia między plikami i to załatwia sprawę.
blispr

Zwróć uwagę na znak nowej linii ... Właśnie odkryłem, że \nbędzie on również uwzględniony podczas porównywania.
Bin


28

Możesz użyć diffnarzędzia w systemie Linux, aby porównać dwa pliki. Możesz użyć opcji --changed-group-format i --unchanged-group-format do filtrowania wymaganych danych.

Aby wybrać odpowiednią grupę dla każdej opcji, można skorzystać z trzech opcji:

  • '% <' pobiera wiersze z PLIKU1

  • '%>' pobiera linie z PLIKU2

  • '' (pusty ciąg) do usuwania linii z obu plików.

Np .: diff --changed-group-format = "% <" --unchanged-group-format = "" file1.txt file2.txt

[root@vmoracle11 tmp]# cat file1.txt 
test one
test two
test three
test four
test eight
[root@vmoracle11 tmp]# cat file2.txt 
test one
test three
test nine
[root@vmoracle11 tmp]# diff --changed-group-format='%<' --unchanged-group-format='' file1.txt file2.txt 
test two
test four
test eight

27

Jeśli wolisz styl wyjściowy diff z git diff, możesz użyć go z --no-indexflagą, aby porównać pliki spoza repozytorium git:

git diff --no-index a.txt b.txt

Używając kilku plików z około 200k ciągami nazw plików w każdym, porównałem (za pomocą wbudowanego timepolecenia) to podejście w porównaniu z niektórymi innymi odpowiedziami tutaj:

git diff --no-index a.txt b.txt
# ~1.2s

comm -23 <(sort a.txt) <(sort b.txt)
# ~0.2s

diff a.txt b.txt
# ~2.6s

sdiff a.txt b.txt
# ~2.7s

vimdiff a.txt b.txt
# ~3.2s

commwydaje się być najszybszym jak dotąd, podczas gdy git diff --no-indexwydaje się być najszybszym podejściem do wyjścia w stylu diff.


Aktualizacja 2018-03-25 W rzeczywistości możesz pominąć --no-indexflagę, chyba że jesteś w repozytorium git i chcesz porównać nieśledzone pliki w tym repozytorium. Ze stron podręcznika :

Ta forma służy do porównania podanych dwóch ścieżek w systemie plików. Możesz pominąć opcję --no-index podczas uruchamiania polecenia w drzewie roboczym kontrolowanym przez Git i co najmniej jedna ze ścieżek wskazuje poza drzewem roboczym lub podczas uruchamiania polecenia poza drzewem roboczym kontrolowanym przez Git.




4

Użyj comm -13 (wymaga posortowanych plików) :

$ cat file1
one
two
three

$ cat file2
one
two
three
four

$ comm -13 <(sort file1) <(sort file2)
four

1

Oto moje rozwiązanie:

mkdir temp
mkdir results
cp /usr/share/dict/american-english ~/temp/american-english-dictionary
cp /usr/share/dict/british-english ~/temp/british-english-dictionary
cat ~/temp/american-english-dictionary | wc -l > ~/results/count-american-english-dictionary
cat ~/temp/british-english-dictionary | wc -l > ~/results/count-british-english-dictionary
grep -Fxf ~/temp/american-english-dictionary ~/temp/british-english-dictionary > ~/results/common-english
grep -Fxvf ~/results/common-english ~/temp/american-english-dictionary > ~/results/unique-american-english
grep -Fxvf ~/results/common-english ~/temp/british-english-dictionary > ~/results/unique-british-english

2
Czy wypróbowałeś inne rozwiązania? Czy jedno z tych rozwiązań było dla Ciebie przydatne? Twoje pytanie jest wystarczająco ogólne, aby przyciągnąć wielu użytkowników, ale twoja odpowiedź jest bardziej szczegółowa dla mojego gustu ... W moim przypadku sdiff -s file1 file2była przydatna.
Metafaniel

@Metafaniel moje rozwiązanie nie używa polecenia sdiff. Używa tylko wbudowanych poleceń Linuksa, aby rozwiązać problem.
Ali Imran

-1

Używanie do tego awk. Pliki testowe:

$ cat a.txt
one
two
three
four
four
$ cat b.txt
three
two
one

Awk:

$ awk '
NR==FNR {                    # process b.txt  or the first file
    seen[$0]                 # hash words to hash seen
    next                     # next word in b.txt
}                            # process a.txt  or all files after the first
!($0 in seen)' b.txt a.txt   # if word is not hashed to seen, output it

Wyprowadzane są duplikaty:

four
four

Aby uniknąć duplikatów, dodaj każde nowo poznane słowo w pliku.txt do seenkrzyżyka:

$ awk '
NR==FNR {
    seen[$0]
    next
}
!($0 in seen) {              # if word is not hashed to seen
    seen[$0]                 # hash unseen a.txt words to seen to avoid duplicates 
    print                    # and output it
}' b.txt a.txt

Wynik:

four

Jeśli listy słów są oddzielone przecinkami, na przykład:

$ cat a.txt
four,four,three,three,two,one
five,six
$ cat b.txt
one,two,three

musisz zrobić kilka dodatkowych okrążeń ( forpętli):

awk -F, '                    # comma-separated input
NR==FNR {
    for(i=1;i<=NF;i++)       # loop all comma-separated fields
        seen[$i]
    next
}
{
    for(i=1;i<=NF;i++)
        if(!($i in seen)) {
             seen[$i]        # this time we buffer output (below):
             buffer=buffer (buffer==""?"":",") $i
        }
    if(buffer!="") {         # output unempty buffers after each record in a.txt
        print buffer
        buffer=""
    }
}' b.txt a.txt

Wyjście tym razem:

four
five,six
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.