Ответ 1
Вы можете добиться этого, контролируя форматирование старых/новых/неизменных строк в выводе GNU diff
:
diff --new-line-format="" --unchanged-line-format="" file1 file2
Для этого нужно отсортировать входные файлы, чтобы они работали. С помощью bash
(и zsh
) вы можете сортировать на месте с заменой процесса <( )
:
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
В приведенных выше новых и неизменных строках подавляются, поэтому выводятся только измененные (т.е. удаленные строки в вашем случае). Вы также можете использовать несколько опций diff
, которые не предлагают другие решения, такие как -i
игнорировать регистр или различные параметры пробелов (-E
, -b
, -v
и т.д.) Для менее строгого соответствия.
Объяснение
Параметры --new-line-format
, --old-line-format
и --unchanged-line-format
позволяют вам контролировать способ diff
форматировать различия, аналогичные спецификаторам формата printf
. Эти параметры форматируют новые (добавленные), старые (удаленные) и неизменные строки соответственно. Установка одного на пустой "" предотвращает вывод этой линии.
Если вы знакомы с унифицированным форматом diff, вы можете частично воссоздать его с помощью:
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
Спецификатор %L
- это строка, и мы префикс каждый с "+" "-" или "", например diff -u
(обратите внимание, что он выводит только разности, ему не хватает строк ---
+++
и @@
в верхней части каждого сгруппированного изменения).
Вы также можете использовать это для выполнения других полезных вещей, таких как количество каждой строки с помощью %dn
.
Метод diff
(наряду с другими предложениями comm
и join
) генерирует только ожидаемый результат с отсортированным входом, хотя вы можете использовать <(sort ...)
для сортировки. Вот простой awk
(nawk) script (вдохновленный сценариями, связанными с Konsolebox), который принимает произвольно упорядоченные входные файлы и выводит недостающие строки в том порядке, в котором они происходят в файле1.
# output lines in file1 that are not in file2
BEGIN { FS="" } # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; } # file1, index by lineno
(NR!=FNR) { ss2[$0]++; } # file2, index by string
END {
for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}
Это сохраняет все содержимое файла1 по строкам в индексированном массиве с номером строки ll1[]
, а все содержимое файла2 строит строку в индексированном ассоциативном массиве ss2[]
. После чтения обоих файлов выполните итерацию по ll1
и используйте оператор in
, чтобы определить, присутствует ли строка в файле1 в файле2. (При наличии дубликатов это будет иметь другой выход для метода diff
.)
В случае, если файлы достаточно велики, что их сохранение вызывает проблему с памятью, вы можете торговать процессором для памяти, сохраняя только файл1 и удаляя совпадения по мере чтения файла2.
BEGIN { FS="" }
(NR==FNR) { # file1, index by lineno and string
ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) { # file2
if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}
Вышеприведенное содержимое хранит все содержимое файла1 в двух массивах, индексируется номером строки ll1[]
, индексируется по контенту ss1[]
. Затем, когда файл2 считывается, каждая соответствующая строка удаляется из ll1[]
и ss1[]
. В конце выводятся оставшиеся строки из файла1, сохраняя исходный порядок.
В этом случае с указанной проблемой вы также можете разделить и победить с помощью GNU split
(фильтрация - расширение GNU), повторные прогонки с кусками файла1 и чтение файла2 полностью каждый раз:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
Обратите внимание на использование и размещение -
, что означает stdin
в командной строке gawk
. Это обеспечивается split
из файла1 в кусках 20000 строк за вызов.
Для пользователей в системах, отличных от GNU, почти наверняка есть пакет GNU coreutils, который можно получить, в том числе на OSX как часть Apple Xcode инструменты, которые обеспечивают GNU diff
, awk
, хотя и только POSIX/BSD split
, а не версию GNU.