Вы можете добиться этого, управляя форматированием старых / новых / неизмененных строк в 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