Порівняння двох файлів у терміналі Linux


168

Є два файли під назвою "a.txt" і "b.txt", обидва мають список слів. Тепер я хочу перевірити, які слова є зайвими в "a.txt", а не в "b.txt" .

Мені потрібен ефективний алгоритм, оскільки мені потрібно порівняти два словники.


27
diff a.txt b.txtнедостатньо?
СпасибоForAllTheFish

Чи можуть слова зустрічатися кілька разів у кожному файлі? Чи можете ви сортувати файли?
Базиль Старинкевич

Мені потрібні лише ті слова, яких немає у "b.txt" і є в a.txt
Алі Імран

Відповіді:


343

якщо встановлено vim, спробуйте це:

vimdiff file1 file2

або

vim -d file1 file2

ви знайдете це фантастичним.введіть тут опис зображення


9
безумовно, приголомшливий, хороший в дизайні і легко з'ясувати відмінності. Охмигод
Дзен

1
Ваша відповідь приголомшлива, але мій учитель вимагав від мене не використовувати жодної бібліотечної функції: P
Алі Імран

1
Який чудовий інструмент! Це надзвичайно корисно.
user1205577

1
Які значення мають ці кольори?
зигімантус

1
Кольорові коди означають, що вони відрізняються у двох файлах. @zygimantus
Li

73

Сортуйте їх та використовуйте comm:

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

commпорівнює (сортується) вхідні файли та за замовчуванням виводить три стовпці: рядки, унікальні для a, рядки, унікальні для b, і рядки, які є в обох. Вказавши -1, -2і / або -3ви можете придушити відповідний висновок. Тому comm -23 a bперераховуються лише записи, які є унікальними для. Я використовую <(...)синтаксис для сортування файлів під час льоту, якщо вони вже відсортовані, вам цього не потрібно.


Я додав власну відповідь, використовуючи лише команди grep, скажіть, будь ласка, чи це ефективніше?
Алі Імран

3
@AliImran, commє більш ефективним, тому що він виконує роботу за один цикл, не зберігаючи весь файл у пам'яті. Оскільки ви використовуєте словники, які, швидше за все, вже відсортовані, вам навіть не потрібні sort. Використання grep -f file1 file2з іншого боку , буде завантажити всю file1в пам'ять і порівняти кожен рядок в file2з усіма цими записами, що є набагато менш ефективним. Це в основному корисно для невеликих, несолодких -f file1.
Андерс Йоханссон

1
Дякуємо @AndersJohansson за те, що поділився командою "comm". Дійсно, справді. Мені часто доводиться робити зовнішні з'єднання між файлами, і це робить трюк.
блиспр

Зверніть увагу на новий символ рядка ... Я щойно виявив, що \nтакож буде включено для порівняння.
Бін


28

Ви можете використовувати diffінструмент в Linux для порівняння двох файлів. Для фільтрації необхідних даних можна використовувати параметри --changed-group та --unchanged-group-format .

Наступні три варіанти можна використовувати для вибору відповідної групи для кожного варіанта:

  • '% <' отримати рядки з FILE1

  • '%>' отримати рядки з FILE2

  • '' (порожній рядок) для видалення рядків з обох файлів.

Напр .: 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

Якщо ви віддаєте перевагу стилю виводу diff від git diff, ви можете використовувати його з --no-indexпрапором для порівняння файлів, які не знаходяться у сховищі git:

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

Використовуючи пару файлів з приблизно 200k рядків імен файлів у кожному, я порівняв (за допомогою вбудованої timeкоманди) цей підхід проти деяких інших відповідей тут:

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

comm здається, найшвидший на сьогоднішній день git diff --no-index здається, це найшвидший підхід для виведення різного стилю.


Оновлення 2018-03-25 Правда ви можете фактично опустити --no-indexпрапорець, якщо ви не знаходитесь у сховищі git і не хочете порівнювати непотрібні файли у цьому сховищі. З чоловічої сторінки :

Ця форма полягає у порівнянні заданих двох шляхів у файловій системі. Ви можете опустити параметр --no-index при виконанні команди в робочому дереві, керованому Git, і принаймні в одній з точок шляхів поза робочим деревом або при запуску команди поза робочим деревом, керованим Git.




4

Використовувати comm -13 (потрібні відсортовані файли) :

$ cat file1
one
two
three

$ cat file2
one
two
three
four

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

1

Ось моє рішення для цього:

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
Ви пробували будь-яке з інших рішень? Чи було вам корисне одне з цих рішень? Ваше запитання є загальним для багатьох користувачів, але ваша відповідь більш конкретна на мій смак ... Для мого конкретного випадку sdiff -s file1 file2було корисно.
Метафаніел

@Metafaniel моє рішення не використовує команду sdiff. Для вирішення проблеми він використовує лише Linux, вбудований у команди.
Алі Імран

-1

Використання awk для цього. Тестові файли:

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

The 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

Дублікати викреслюються:

four
four

Щоб уникнути дублікатів, додайте в seenхеш кожне нещодавно зустрінене слово в a.txt :

$ 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

Вихід:

four

Якщо списки слів розділені комами, наприклад:

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

вам доведеться зробити кілька додаткових кіл ( forциклів):

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

Виведіть цей раз:

four
five,six
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.