Как сохранить дублирующиеся строки при сравнении файлов: подробное руководство

Если вы работаете с текстовыми файлами в Linux, рано или поздно столкнётесь с ситуацией, когда стандартные инструменты вроде grep не дают нужного результата. Представьте: есть два файла – один содержит список искомых слов (включая повторы), другой представляет собой базу данных с путями. Задача – собрать в третий файл все строки из второго файла, соответствующие словам из первого, сохранив дубликаты и исходный порядок. Звучит просто, но есть нюансы.

Попробуем разобраться на конкретном примере. Допустим, в File1.txt у вас повторяются строки «dog» и «cat» – это шаблоны для поиска. File2.txt содержит уникальные записи формата «животное путь_к_файлу». При использовании классической команды grep -wF -f File1.txt File2.txt вы получите только по одному вхождению для каждого совпадения, хотя ожидаете сохранить все дубли. Почему так происходит? Дело в том, что grep по умолчанию фильтрует уникальные строки при поиске по шаблонам из файла.

Решение через awk: где grep бессилен

Здесь на помощь приходит awk – мощный инструмент для обработки текста. Вот рабочий вариант скрипта:

gawk 'FNR==NR{ a[i++]=$0 }
      FNR!=NR{ for(i in a) { if ($0 ~ a[i]) print $0 }}' File1.txt File2.txt > File3.txt

Разберём по шагам:

1. FNR==NR – условие, которое истинно только при обработке первого файла (File1.txt). Строки последовательно записываются в массив a[].
2. FNR!=NR – переход ко второму файлу (File2.txt). Для каждой его строки выполняется проверка через цикл по всем элементам массива a[].
3. $0 ~ a[i] – оператор соответствия, где $0 означает текущую строку File2.txt, а a[i] — элемент из File1.txt.

Кстати, обратите внимание, если вы пишете данный код в одну строку, вам потребуется точка с запятой между блоками. В данном случае отсутствует точка с запятой, потому что блоки разделены переводом строки, что допустимо в gawk.

Почему это работает и как избежать ошибок

Главное преимущество подхода – полный контроль над процессом сопоставления. В отличие от grep, который оптимизирован для скорости и «не видит» дубликатов в шаблонах, awk последовательно проверяет каждую строку File2.txt на соответствие всем элементам File1.txt, включая повторяющиеся.

Типичные проблемы и их решения:

  • Неправильный порядок файлов – сначала всегда должен идти File1.txt, потом File2.txt. Если перепутать, массив заполнится данными из второго файла, и совпадений не будет.
  • Спецсимволы в File1.txt – например, точки или звёздочки. Они интерпретируются как регулярные выражения. Чтобы отключить это, замените ~ на == в условии: if ($0 == a[i]).
  • Медленная работа с большими файлами – цикл по всем элементам массива может стать узким местом. Для оптимизации используйте хеш-таблицы: if ($1 in patterns), предварительно загрузив ключи из File1.txt.

Интересный момент: если в File2.txt есть строки с несколькими совпадениями (например, «catdog d:catdog.fbx»), текущий скрипт выведет их столько раз, сколько найдёт соответствий в File1.txt. Это не всегда желательно – чтобы избежать такого, добавьте break после print $0, прерывая цикл при первом совпадении.

Для тех, кто хочет поэкспериментировать, попробуйте модифицировать скрипт:

gawk 'FNR==NR{ a[$0]++; next }
      { for (pattern in a) {
          if ($0 ~ pattern) {
            for (i=1; i<=a[pattern]; i++) print
            break
          }
        }
      }' File1.txt File2.txt

Этот вариант учитывает количество вхождений каждого шаблона и выводит совпадения ровно столько раз, сколько они встречались в File1.txt. Полезно, когда важно сохранить точное число дублей, а не просто все возможные совпадения.

Новое
Интересное