Сравнение двух файлов в терминале Linux


168

Есть два файла с именами «a.txt» и «b.txt», оба имеют список слов. Теперь я хочу проверить, какие слова являются дополнительными в «a.txt» и не находятся в «b.txt» .

Мне нужен эффективный алгоритм, так как мне нужно сравнить два словаря.


27
diff a.txt b.txtнедостаточно?
Спасибо за все TheFish

Могут ли слова встречаться в каждом файле несколько раз? Вы можете отсортировать файлы?
Василий Старынкевич,

мне нужны только те слова, которых нет в "b.txt" и которые присутствуют в a.txt
Али Имран

Ответы:


343

если у вас установлен vim, попробуйте это:

vimdiff file1 file2

или

vim -d file1 file2

Вы найдете это фантастическим.введите описание изображения здесь


9
определенно потрясающий, хороший дизайн и легко узнать различия. Ohmygod
Дзен

1
Ваш ответ потрясающий, но мой учитель потребовал, чтобы я не использовал библиотечную функцию: P
Али Имран,

1
Какой отличный инструмент! Это очень полезно.
user1205577 22.09.15

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". Это действительно изящно. Мне часто приходится делать внешние соединения между файлами, и это делает свое дело.
blispr

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


28

Вы можете использовать diffинструмент в Linux, чтобы сравнить два файла. Вы можете использовать параметры --changed-group-format и --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

Используя пару файлов с примерно 200-тысячными строками имен в каждой, я сравнил (с помощью встроенной 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кажется самым быстрым подходом для вывода в стиле diff.


Обновление 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

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

Чтобы избежать дубликатов, добавьте каждое вновь встреченное слово в a.txt в seenхеш:

$ 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.