Команда Unix для поиска строк, общих в двух файлах


179

Я уверен, что однажды нашел команду unix, которая могла бы печатать общие строки из двух или более файлов, кто-нибудь знает ее название? Это было намного проще, чем diff.


5
Ответы на этот вопрос не обязательно то, что все захотят, так как commтребует отсортированных входных файлов. Если вы хотите просто построчно, это здорово. Но если вы хотите то, что я бы назвал «антидифф», commне делайте эту работу.
Роберт П. Голдман

@ RobertP.Goldman есть способ получить общее между двумя файлами, когда file1 содержит частичный образец, как pr-123-xy-45и file2 содержит ec11_orop_pr-123-xy-45.gz. Мне нужен file3, содержащийec11_orop_pr-123-xy-45.gz
Чандан Чоудхури

Смотрите это для сортировки текстовых файлов построчно
y2k-shubham

Ответы:


216

Команда, которую вы ищете, это comm. например:-

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

Вот:

-1 : подавить столбец 1 (строки уникальны для 1.sorted.txt)

-2 : подавить столбец 2 (строки уникальны для 2.sorted.txt)


27
Типичное использование: comm -12 1.sorted.txt 2.sorted.txt
Федир РИХТИК

45
Хотя для comm нужны отсортированные файлы, вы можете использовать grep -f file1 file2, чтобы получить общие строки обоих файлов.
Ферди

2
@ferdy (повторение моего комментария из вашего ответа, поскольку ваш по сути повторный ответ, опубликованный в виде комментария) grepделает некоторые странные вещи, которые вы, возможно, не ожидаете. В частности, все в 1.txtбудет интерпретироваться как регулярное выражение, а не простая строка. Кроме того, любая пустая строка в 1.txtбудет соответствовать всем строкам в 2.txt. Так grepбудет работать только в очень специфических ситуациях. Вы по крайней мере хотели бы использовать fgrep(или grep -f), но пустая строка, вероятно, нанесет ущерб этому процессу.
Кристофер Шульц

11
См Ферди «s ответ ниже, и Кристофер Шульца » s и мои комментарии к нему. TL; DR - использовать grep -F -x -f file1 file2.
Джонатан Леффлер

1
@bapors: я ответил на вопросы и ответы, ответив на которые сам, как получить выходные данные commкоманды в 3 отдельных файла? Ответ был слишком велик, чтобы здесь было удобно.
Джонатан Леффлер

62

Чтобы легко применить команду comm к несортированным файлам, используйте процесс подстановки Bash :

$ 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

Таким образом, файлы abc и def имеют одну общую строку, одну с «132». Использование comm для несортированных файлов:

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

Последняя строка не выдала, общая линия не была обнаружена.

Теперь используйте comm для отсортированных файлов, сортируя файлы с подстановкой процесса:

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

Теперь мы получили линию 132!


2
так что ... sort abc > abc.sorted, sort dev > def.sortedа потом comm -12 abc.sorted def.sorted?
Никана Рекламикс

1
@NikanaReklawyks И затем не забудьте удалить временные файлы впоследствии, и справиться с очисткой в ​​случае ошибки. Во многих случаях замена процесса также будет происходить намного быстрее, поскольку вы можете избежать дискового ввода-вывода, пока результаты помещаются в память.
tripleee

29

Чтобы дополнить Perl-однострочник, вот его awkэквивалент:

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

Это будет считывать все строки из file1массива arr[], а затем проверять каждую строку, file2если она уже существует в массиве (то есть file1). Найденные строки будут напечатаны в том порядке, в котором они отображаются file2. Обратите внимание, что для сравнения in arrиспользуется вся строка от file2индекса до массива, поэтому он будет сообщать только о точных совпадениях во всех строках.


2
Это правильный ответ. Ни один из других не может быть заставлен работать вообще (те я не пробовал perl, потому что).
Огромное

1
Сохранение порядка при отображении общих строк может быть действительно полезным в некоторых случаях, из-за которых исключается связь.
Tuxayo

1
В случае, если кто-то хочет сделать то же самое на основе определенного столбца, но не знает awk, просто замените оба $ 0 на $ 5, например, для столбца 5, чтобы получить строки, разделенные на 2 файла с одинаковыми словами в столбце 5
ФатихСаригол

24

Может ты имеешь ввиду comm?

Сравните отсортированные файлы FILE1 и FILE2 построчно.

Без опций выведите три столбца. Первый столбец содержит строки, уникальные для FILE1, второй столбец содержит строки, уникальные для FILE2, а третий столбец содержит строки, общие для обоих файлов.

Секрет в поиске этой информации - информационные страницы. Для программ GNU они намного более подробны, чем их man-страницы. Попробуйте, info coreutilsи он перечислит вам все маленькие полезные утилиты.


19

Пока

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

дает вам различия двух файлов (что в 2.txt, а не в 1.txt), вы можете легко сделать

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

собрать все общие линии, которые должны обеспечить простое решение вашей проблемы. Если у вас есть отсортированные файлы, вы должны принять, commтем не менее. С уважением!


2
grepделает некоторые странные вещи, которые вы не могли бы ожидать. В частности, все в 1.txtбудет интерпретироваться как регулярное выражение, а не простая строка. Кроме того, любая пустая строка в 1.txtбудет соответствовать всем строкам в 2.txt. Так что это будет работать только в очень специфических ситуациях.
Кристофер Шульц

13
@ChristopherSchultz: Возможно обновить этот ответ, чтобы лучше работать, используя grepнотации POSIX , которые поддерживаются в grepбольшинстве современных вариантов Unix. Добавьте -F(или используйте fgrep) для подавления регулярных выражений. Добавьте -x(для точного), чтобы соответствовать только целые строки.
Джонатан Леффлер

Почему мы должны брать commотсортированные файлы?
Ulysse BN

2
@UlysseBN commможет работать с произвольно большими файлами до тех пор, пока они сортируются, потому что для этого требуется всего лишь три строки в памяти (я предполагаю, что GNU commдаже знает, что нужно сохранить только префикс, если строки действительно длинные). grepРешение должно сохранять все поисковые выражения в памяти.
tripleee

9

Если два файла еще не отсортированы, вы можете использовать:

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

и это будет работать, избегая сообщения об ошибке comm: file 2 is not in sorted order при выполнении comm -12 a.txt b.txt.


Вы правы, но это по сути повторяет другой ответ , который действительно не дает никакой выгоды. Если вы решите ответить на более старый вопрос, на котором есть точные и правильные ответы, добавление нового ответа в конце дня может не дать вам никакой оценки. Если у вас есть какая-то отличительная новая информация, или вы убеждены, что все остальные ответы неверны, обязательно добавьте новый ответ, но «еще один ответ», дающий ту же основную информацию спустя долгое время после того, как вопрос был задан, обычно выигрывает ». Я не заработаю тебе много кредитов.
Джонатан Леффлер

Я даже не видел этот ответ @JonathanLeffler, потому что эта часть была в самом конце ответа, смешанного с другими элементами ответа раньше. Хотя другой ответ является более точным, мое преимущество, я думаю, заключается в том, что для того, кто хочет быстрого решения, нужно будет прочитать только две строки. Иногда мы ищем подробный ответ, а иногда мы спешим, и быстрый для чтения готовый к вставке ответ - это хорошо.
Basj

Кроме того, я не забочусь о кредите / репутации, я не публиковал для этой цели.
Basj

1
Также обратите внимание, что синтаксис подстановки процессов <(command)не переносится в оболочку POSIX, хотя он работает в Bash и некоторых других.
tripleee

8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

это работает лучше, чем commкоманда, поскольку она ищет каждую строку file1в, file2где commбудет сравниваться, только если строка nв file1равна строке nв file2.
teriiehina

1
@teriiehina: нет; commне просто сравнивает строку N в файле1 со строкой N в файле2. Он может прекрасно управлять серией строк, вставленных в любой файл (что, конечно, эквивалентно удалению серии строк из другого файла). Это просто требует, чтобы входы были в отсортированном порядке.
Джонатан Леффлер

Лучше, чем commответы, если кто-то хочет сохранить порядок. Лучше, чем awkотвечать, если никто не хочет дубликатов.
Tuxayo



3

На ограниченной версии Linux (например, QNAP (nas), над которой я работал):

  • комм не существовало
  • grep -f file1 file2может вызвать некоторые проблемы, как сказал @ChristopherSchultz, и использование grep -F -f file1 file2было очень медленным (более 5 минут - не завершено - более 2-3 секунд с методом ниже для файлов размером более 20 МБ)

Итак, вот что я сделал:

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

Если files.same.sortedон будет в том же порядке, что и исходные, то добавьте эту строку для того же порядка, что и файл1:

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

или для того же порядка, что и file2:

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

2

Просто для справки, если кто-то все еще ищет, как это сделать для нескольких файлов, см. Связанный ответ « Поиск совпадающих строк во многих файлах».


Объединяя эти два ответа ( ans1 и ans2 ), я думаю, что вы можете получить нужный результат без сортировки файлов:

#!/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

Просто сохраните его, дайте ему права на выполнение ( chmod +x compareFiles.sh) и запустите его. Он примет все файлы, присутствующие в текущем рабочем каталоге, и выполнит сравнение «все против всех», оставив в файле «Match_Lines» результат.

Что нужно улучшить:

  • Пропустить каталоги
  • Избегайте сравнения всех файлов два раза (файл1 против файла2 и файл2 против файла1).
  • Может быть, добавить номер строки рядом с соответствующей строкой

-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

Это должно сделать это.


1
Возможно, вам следует использовать, rm -f file3.txtесли вы собираетесь удалить файл; это не сообщит ни о какой ошибке, если файл не существует. OTOH, это не было бы необходимо, если бы ваш скрипт просто отображал стандартный вывод, позволяя пользователю сценария выбирать, куда выводить данные. В конечном счете, вы, вероятно, захотите использовать $1и $2(аргументы командной строки) вместо фиксированных имен файлов ( file1.outи file2.out). Это оставляет алгоритм: он будет медленным. Это будет читать file2.outодин раз для каждой строки в file1.out. Это будет медленно, если файлы большие (скажем, несколько килобайт).
Джонатан Леффлер

Хотя это может номинально работать, если у вас есть входные данные, которые не содержат метасимволов оболочки (подсказка: посмотрите, какие предупреждения вы получаете от shellcheck.net ), этот наивный подход ужасно неэффективен. Инструмент, подобный тому, grep -Fкоторый читает один файл в память, а затем делает один проход над другим, избегает многократного зацикливания обоих входных файлов.
tripleee
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.