Команда Unix для пошуку рядків, поширених у двох файлах


179

Я впевнений, що одного разу я знайшов команду unix, яка могла б надрукувати загальні рядки з двох або більше файлів, хтось знає її ім'я? Це було набагато простіше, ніж diff.


5
Відповіді на це запитання не обов'язково потрібні всім, оскільки commпотрібні відсортовані вхідні файли. Якщо ви хочете просто спільно за рядком, це здорово. Але якщо ви хочете, що я б назвав "анти-різний", commне обійдеться.
Роберт П. Голдман

@ RobertP.Goldman є спосіб отримати спільне між двома файлами, коли file1 містить частковий візерунок типу, pr-123-xy-45а file2 містить ec11_orop_pr-123-xy-45.gz. Мені потрібен файл3, що містить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
Хоча кому потрібні сортовані файли, ви можете взяти 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 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?
Nikana Reklawyks

1
@NikanaReklawyks А потім не забудьте потім видалити тимчасові файли та впорайтеся з очищенням у разі помилки. У багатьох сценаріях підміна процесу також буде набагато швидшою, оскільки ви можете уникати вводу / виводу диска, якщо результати вписуються в пам'ять.
трійка

29

Щоб доповнити однолінійку Perl, ось його awkеквівалент:

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

Це буде читати всі рядки з file1масиву arr[], а потім перевіряти, file2чи не існує він у кожному масиві (тобто file1). Знайдені рядки будуть надруковані в тому порядку, в якому вони відображаються file2. Зауважте, що для порівняння in arrвикористовується весь рядок від file2індексу до масиву, тому він повідомлятиме лише про точні збіги у всіх рядках.


2
ЦЕ (!) - правильна відповідь. Ніхто з інших не може змусити себе працювати загалом (я їх не пробував perl, тому що). Дякую мільйон, пані
entonio

1
Збереження порядку під час відображення загальних рядків може бути дуже корисним у деяких випадках, які б виключали кому через це.
tuxayo

1
Якщо хтось хоче зробити те саме, що базується на певному стовпчику, але не знає awk, просто замініть обидва $ 0 на $ 5, наприклад на колонку 5, щоб ви отримали спільні рядки у двох файлах з однаковими словами у графі 5
FatihSarigol

24

Може, ти маєш на увазі comm?

Порівняйте відсортовані файли FILE1 та FILE2 рядок за рядком.

Не маючи жодних варіантів, виведіть вихід з трьох стовпців. Перша колонка містить рядки, унікальні для FILE1, друга колонка містить рядки, унікальні для FILE2, а третя колонка містить рядки, загальні для обох файлів.

Секрет пошуку цієї інформації - це інформаційні сторінки. Що стосується програм GNU, вони набагато детальніше, ніж їх довідкові сторінки. Спробуйте, 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за сортовані файли?
Уліссе БН

2
@UlysseBN commможе працювати з довільно великими файлами до тих пір, поки вони відсортовані, тому що йому потрібно колись утримувати три пам’яті у пам’яті (я здогадуюсь, GNU commнавіть знає, щоб зберегти лише префікс, якщо рядки дійсно довгі). grepРішення повинно зберігати всі пошукові вираження в пам'яті.
трійка

9

Якщо два файли ще не відсортовано, ви можете використовувати:

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

і воно буде працювати, уникаючи повідомлення про помилку comm: file 2 is not in sorted order під час роботи comm -12 a.txt b.txt.


Ви маєте рацію, але це, по суті, повторює ще одну відповідь , яка насправді не дає ніякої користі. Якщо ви вирішите відповісти на старіші запитання, на які є чітко встановлені та правильні відповіді, додавання нової відповіді в кінці дня може не отримати вам жодної заслуги. Якщо у вас є якась нова відмінна інформація або ви впевнені, що інші відповіді невірні, будь-ласка, додайте нову відповідь, але "ще одна відповідь", що дає ту саму основну інформацію довгий час після того, як запитання, як правило, вигравали " t заробляєш багато кредиту.
Джонатан Леффлер

Я навіть не бачив цієї відповіді @JonathanLeffler, тому що ця частина була в самому кінці відповіді, змішана з іншими елементами відповіді раніше. Хоча інша відповідь є більш точною, моя користь, я думаю, що для того, хто хоче швидкого рішення, доведеться прочитати лише 2 рядки. Іноді ми шукаємо детальну відповідь, а іноді поспішаємо, і відповідь, яка швидко читається, готова до вставки, чудово.
Бась

Крім того, я не переймаюся кредитом / реп., Я не розміщував з цією метою.
Бась

1
Зауважте також, що синтаксис заміщення процесу <(command)не переноситься на оболонку POSIX, хоча він працює в Bash та деяких інших.
трійка

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

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

1
@teriiehina: Ні; commне просто порівнює рядок N у file1 з рядком N у file2. Він може чудово керувати рядом рядків, вставлених у будь-який файл (що, звичайно, еквівалентно видаленню серії рядків з іншого файлу, звичайно). Це просто вимагає введення даних в упорядкованому порядку.
Джонатан Леффлер

Краще, ніж 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" результат.

Що слід покращити:

  • Пропустіть каталоги
  • Уникайте порівняння всіх файлів у два рази (file1 vs file2 та file2 vs file1).
  • Можливо, додайте номер рядка поруч із відповідним рядком

-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читає один файл в пам'яті, а потім проходить один прохід над іншим, уникає повторного циклічного перегляду обох вхідних файлів.
трійка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.