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
Другие вопросы по тегам