У меня есть два больших файла (наборы имен файлов). Примерно 30.000 строк в каждом файле. Я пытаюсь найти быстрый способ найти строки в file1, которых нет в file2.
Например, если это файл1:
line1
line2
line3
И это файл2:
line1
line4
line5
Тогда мой результат / вывод должен быть:
line2
line3
Это работает:
grep -v -f file2 file1
Но это очень, очень медленно, когда используется на моих больших файлах.
Я подозреваю, что есть хороший способ сделать это с помощью diff (), но на выходе должны быть только строки, ничего больше, и я не могу найти переключатель для этого.
Может кто-нибудь помочь мне найти быстрый способ сделать это, используя bash и базовые бинарные файлы Linux?
РЕДАКТИРОВАТЬ: Чтобы продолжить мой вопрос, это лучший способ, который я нашел до сих пор с использованием diff ():
diff file2 file1 | grep '^>' | sed 's/^>\ //'
Конечно, должен быть лучший способ?
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
cat file1 file2 file2 | sort | uniq --unique
см. Мой ответ ниже.Ответы:
Вы можете добиться этого, управляя форматированием старых / новых / неизмененных строк в
diff
выводе GNU :Входные файлы должны быть отсортированы, чтобы это работало. С помощью
bash
(иzsh
) вы можете отсортировать на месте с заменой процесса<( )
:В вышеупомянутых новые и неизмененные строки подавляются, поэтому выводятся только измененные (т.е. удаленные строки в вашем случае). Вы также можете использовать несколько
diff
вариантов , которые другие решения не предлагают, например , как-i
игнорировать случай, или различные варианты пробельные (-E
,-b
, и-v
т.д.) для менее строгого соответствия.объяснение
Опции
--new-line-format
,--old-line-format
и--unchanged-line-format
позволяют вам контролировать способdiff
форматирования различий, аналогичноprintf
спецификаторам формата. Эти параметры форматируют новые (добавленные), старые (удаленные) и неизмененные строки соответственно. Установка одного в "" предотвращает вывод такой линии.Если вы знакомы с унифицированным форматом diff , вы можете частично восстановить его с помощью:
%L
Спецификатор линия в вопросе, и мы префикс друг с «+» «-» или «», какdiff -u
(заметит , что она выводит только различие, это не хватает---
+++
и@@
линий в верхней части каждых сгруппированных изменений). Вы также можете использовать это , чтобы делать другие полезные вещи , как числа каждой строки с%dn
.diff
Метод (наряду с другими предложениямиcomm
иjoin
) производить только ожидаемый результат с отсортированным вводом, хотя вы можете использовать<(sort ...)
для сортировки на месте. Вот простойawk
(nawk) скрипт (навеянный скриптами, связанными в ответе Konsolebox), который принимает произвольно упорядоченные входные файлы и выводит пропущенные строки в порядке их появления в file1.При этом все содержимое файла file1 строка за строкой сохраняется в индексированном массиве с номерами строк
ll1[]
, а все содержимое файла file2 строка за строкой - в индексированном ассоциативном массиве с содержимым строкss2[]
. После того, как оба файла прочитаны, выполните итерациюll1
и используйтеin
оператор, чтобы определить, присутствует ли строка в файле1 в файле2. (Это будет иметь другой вывод дляdiff
метода, если есть дубликаты.)В случае, если файлы достаточно велики, и их хранение приводит к проблемам с памятью, вы можете обменять ЦП на память, сохранив только файл1 и удалив совпадения по пути при чтении файла2.
Выше хранится все содержимое файла file1 в двух массивах, один индексируется по номеру строки
ll1[]
, другой индексируется по содержимому строкиss1[]
. Затем, когда file2 читается, каждая совпадающая строка удаляется изll1[]
иss1[]
. В конце выводятся оставшиеся строки из file1, сохраняя исходный порядок.В этом случае, с указанной выше проблемой, вы также можете разделить и победить, используя GNU
split
(фильтрация - это расширение GNU), повторяющиеся прогоны с кусками файла file1 и чтением файла file2 каждый раз:Обратите внимание на использование и размещение
-
значенияstdin
вgawk
командной строке. Это обеспечиваетсяsplit
из file1 порциями по 20000 строк на вызов.Для пользователей в системах , не GNU, есть почти наверняка GNU Coreutils пакет можно получить, в том числе на OSX в рамках компании Apple Xcode инструментов , который обеспечивает GNU
diff
,awk
хотя только POSIX / BSD ,split
а не версия GNU.источник
diff
: в общем случае входные файлы будут другими,diff
в этом случае возвращается 1 . Считайте это бонусом ;-) Если вы тестируете в сценарии оболочки, 0 и 1 - ожидаемые коды выхода, 2 указывает на проблему.man diff
. Спасибо!Команда comm (сокращение от «common») может быть полезной
comm - compare two sorted files line by line
man
Файл на самом деле вполне читаемый для этого.источник
comm
также имеет возможность проверить, что входные данные отсортированы--check-order
(что, по-видимому, в любом случае, но эта опция вызовет ошибку вместо продолжения). Но чтобы отсортировать файлы, просто сделайте:com -23 <(sort file1) <(sort file2)
и так далееcomm
он вообще не работает. Мне потребовалось некоторое время, чтобы понять, что речь идет об окончаниях строк: даже строки, которые выглядят одинаково, считаются разными, если имеют разные окончания строк. Командаdos2unix
может использоваться для преобразования концов строк CRLF только в LF.Как предложил konsolebox, решение grep для постеров
на самом деле работает отлично (быстро), если вы просто добавляете
-F
опцию, чтобы рассматривать шаблоны как фиксированные строки вместо регулярных выражений. Я проверил это на паре списков ~ 1000 строк, которые мне пришлось сравнить. При-F
перенаправлении вывода grep на это потребовалось 0,031 с (реальное), а без 2,278 с (реальное)wc -l
.Эти тесты также включали
-x
переключатель, который является необходимой частью решения для обеспечения полной точности в случаях, когда файл2 содержит строки, которые соответствуют части, но не всем, одной или нескольким строкам в файле1.Таким образом, решение, которое не требует сортировки входных данных, является быстрым, гибким (чувствительность к регистру и т. Д.):
Это не работает со всеми версиями grep, например, происходит сбой в macOS, где строка в файле 1 будет отображаться как отсутствующая в файле 2, даже если это так, если она соответствует другой строке, которая является ее подстрокой , В качестве альтернативы вы можете установить GNU grep на macOS , чтобы использовать это решение.
источник
-F
этим плохо масштабируется.file2
.-x
опцией, очевидно, использует больше памяти. С помощьюfile2
содержащего 180M слов 6-10 байт моего процесса получилKilled
в ОЗУ машине 32GB ...какова скорость сортировки и сравнения?
источник
Если вам не хватает «причудливых инструментов», например, в каком-то минимальном дистрибутиве Linux, есть решение с просто
cat
,sort
иuniq
:Тест:
Это также относительно быстро, по сравнению с
grep
.источник
--unique
опцию. Вы должны быть в состоянии использовать стандартную опцию POSIX для этого:| uniq -u
seq 1 1 7
создает числа от 1 с шагом 1 до 7, т. Е. 1 2 3 4 5 6 7. И вот вам 2!Он
-t
гарантирует, что он сравнивает всю строку, если у вас есть пробел в некоторых строках.источник
comm
,join
требует , чтобы обе входные строки были отсортированы по полю, на котором вы выполняете операцию соединения.Вы можете использовать Python:
источник
Используйте
combine
изmoreutils
пакета, утилита наборов , которая поддерживаетnot
,and
,or
,xor
операциит.е. дайте мне строки, которые находятся в file1, но не в file2
ИЛИ дайте мне строки в файле1 минус строки в файле2
Примечание:
combine
сортирует и находит уникальные строки в обоих файлах перед выполнением любой операции, ноdiff
не выполняет. Таким образом, вы можете найти различия между выводомdiff
иcombine
.Итак, по сути вы говорите
Найти отдельные строки в файле1 и файле2, а затем дать мне строки в файле1 минус строки в файле2
По моему опыту, это намного быстрее, чем другие варианты
источник
Может помочь использование fgrep или добавление опции -F в grep. Но для более быстрых вычислений вы можете использовать Awk.
Вы можете попробовать один из этих методов Awk:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
источник
Обычно я делаю это, используя
--suppress-common-lines
флаг, хотя учтите, что это работает, только если вы делаете это в формате рядом друг с другом.diff -y --suppress-common-lines file1.txt file2.txt
источник
Я обнаружил, что для меня использование нормального оператора цикла if и for работает отлично.
источник
grep
результатов расширится до нескольких слов, или если какие-либо из вашихfile2
записей могут рассматриваться оболочкой как глобус.