Вы можете добиться этого, управляя форматированием старых / новых / неизмененных строк в diff
выводе GNU :
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) скрипт (навеянный скриптами, связанными в ответе Konsolebox), который принимает произвольно упорядоченные входные файлы и выводит пропущенные строки в порядке их появления в file1.
# 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]
}
При этом все содержимое файла file1 строка за строкой сохраняется в индексированном массиве с номерами строк ll1[]
, а все содержимое файла file2 строка за строкой - в индексированном ассоциативном массиве с содержимым строк 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]
}
Выше хранится все содержимое файла file1 в двух массивах, один индексируется по номеру строки ll1[]
, другой индексируется по содержимому строки ss1[]
. Затем, когда file2 читается, каждая совпадающая строка удаляется из ll1[]
и ss1[]
. В конце выводятся оставшиеся строки из file1, сохраняя исходный порядок.
В этом случае, с указанной выше проблемой, вы также можете разделить и победить, используя GNU split
(фильтрация - это расширение GNU), повторяющиеся прогоны с кусками файла file1 и чтением файла file2 каждый раз:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
Обратите внимание на использование и размещение -
значения stdin
в gawk
командной строке. Это обеспечивается split
из file1 порциями по 20000 строк на вызов.
Для пользователей в системах , не GNU, есть почти наверняка GNU Coreutils пакет можно получить, в том числе на OSX в рамках компании Apple Xcode инструментов , который обеспечивает GNU diff
, awk
хотя только POSIX / BSD , split
а не версия GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt