bash, Linux: установить разницу между двумя текстовыми файлами
У меня есть два файла A
-nodes_to_delete
а также B
-nodes_to_keep
, Каждый файл имеет много строк с числовыми идентификаторами.
Я хочу иметь список числовых идентификаторов, которые находятся в nodes_to_delete
но не в nodes_to_keep
например, http://mathworld.wolfram.com/images/equations/SetDifference/Inline1.gif.
Делать это в базе данных PostgreSQL неоправданно медленно. Любой аккуратный способ сделать это в Bash с помощью инструментов Linux CLI?
ОБНОВЛЕНИЕ: Это, кажется, работа Pythonic, но файлы действительно, действительно большие. Я решил некоторые подобные проблемы, используя uniq
, sort
и некоторые методы теории множеств. Это было примерно на два-три порядка быстрее, чем эквиваленты базы данных.
7 ответов
Кто-то показал мне, как это сделать пару месяцев назад, а потом я не мог найти это некоторое время... и, глядя, я наткнулся на ваш вопрос. Вот:
set_union () {
sort $1 $2 | uniq
}
set_difference () {
sort $1 $2 $2 | uniq -u
}
set_symmetric_difference() {
sort $1 $2 | uniq -u
}
Использование comm
- он будет сравнивать два отсортированных файла построчно
Ответ на вопрос ОП с использованием этого примера настройки приведен ниже. Эта команда вернет строки, уникальные для deleteNodes, а не в keepNodes
comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
объяснение: показать строки, уникальные для deleteNodes, скрыть другие строки
пример настройки
Мы будем использовать keepNodes и deleteNodes. Они используются в качестве несортированного ввода.
$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)
По умолчанию без аргументов comm выводит 3 столбца
unique_to_FILE1
unique_to_FILE2
lines_appear_in_both
Это простой пример comm
без аргументов. Обратите внимание на три столбца.
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
Подавление вывода столбца
Подавить столбец 1, 2 или 3 с -N; обратите внимание, что когда столбец скрыт, пробел уменьшается.
$ comm -1 <(sort keepNodes) <(sort deleteNodes)
ann
bob
$ comm -2 <(sort keepNodes) <(sort deleteNodes)
amber
bob
$ comm -3 <(sort keepNodes) <(sort deleteNodes)
amber
ann
$ comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
ann
$ comm -2 -3 <(sort keepNodes) <(sort deleteNodes)
amber
$ comm -1 -2 <(sort keepNodes) <(sort deleteNodes)
bob
Это изменится изящно, когда вы забудете отсортировать
comm: file 1 is not in sorted order
comm
был специально разработан для этого вида использования, но требует отсортированного ввода.
awk
это, пожалуй, лучший инструмент для этого, так как довольно просто найти разницу в множествах, не требует sort
и предлагает дополнительную гибкость.
awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete
Возможно, например, вы хотели бы найти разницу только в строках, которые представляют неотрицательные числа:
awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
a[$0]
next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
Может быть, вам нужен лучший способ сделать это в postgres, я могу поспорить, что вы не найдете более быстрого способа сделать это с помощью плоских файлов. Вы должны быть в состоянии сделать простое внутреннее соединение и предположить, что оба идентификатора индексированы, что должно быть очень быстрым.
Другое переносимое решение, которое также работает в случае мультимножеств, наборов, допускающих несколько экземпляров элемента, заключается в использовании grep с шаблонами в отдельном файле:
grep -Fvx -f B A
Параметры:
- -f: файл, содержащий список шаблонов, построчно
- -F: обрабатывать шаблоны как строку, а не регулярное выражение
- -x: сопоставить целые строки в A-nodes_to_delete
- -v: инвертировать соответствие (сопоставить, если не совпадает)
Если шаблоны в B не совпадают со строкой в A, команда выводит строку, иначе ничего.
Приятной особенностью этого решения является то, что его можно заставить работать с многоколоночными файлами (для
A
) пока
comm
а также
uniq -u
решения требуют файлов с одним столбцом.
Таким образом, это немного отличается от других ответов. Я не могу сказать, что компилятор C++ - это точно "инструмент Linux CLI", но он работает g++ -O3 -march=native -o set_diff main.cpp
(с приведенным ниже кодом в main.cpp
может сделать свое дело)
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
Чтобы использовать, просто запустите set_diff B A
(не A B
, поскольку B
является nodes_to_keep
) и полученная разница будет напечатана на стандартный вывод.
Обратите внимание, что я отказался от нескольких лучших практик C++, чтобы сделать код проще.
Можно было бы сделать много дополнительных оптимизаций скорости (за счет увеличения памяти). mmap
было бы особенно полезно для больших наборов данных, но это сделало бы код гораздо более сложным.
Поскольку вы упомянули, что наборы данных большие, я подумал, что чтение nodes_to_delete
Строка за раз может быть хорошей идеей для сокращения потребления памяти. Подход, использованный в приведенном выше коде, не особенно эффективен, если в вашем nodes_to_delete
, Также порядок не сохранился.
Что-то проще скопировать и вставить в bash
(то есть, пропуская создание main.cpp
):
g++ -O3 -march=native -xc++ -o set_diff - <<EOF
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
EOF