Подтвердить что ты не робот

Bash, Linux: установите разницу между двумя текстовыми файлами

У меня есть два файла A - nodes_to_delete и B - nodes_to_keep. Каждый файл имеет много строк с числовыми идентификаторами.

Я хочу иметь список числовых идентификаторов, которые находятся в nodes_to_delete но НЕ в nodes_to_keep, например, alt text.

Делать это в базе данных PostgreSQL неоправданно медленно. Любой аккуратный способ сделать это в Bash с помощью инструментов Linux CLI?

ОБНОВЛЕНИЕ: Это, кажется, работа Pythonic, но файлы действительно, действительно большие. Я решил некоторые подобные проблемы, используя uniq, sort и некоторые методы теории множеств. Это было примерно на два-три порядка быстрее, чем эквиваленты базы данных.

4b9b3361

Ответ 2

Кто-то показал мне, как сделать это в пару месяцев назад, а потом я не мог найти его какое-то время... и, глядя, я наткнулся на ваш вопрос. Вот он:

set_union () {
   sort $1 $2 | uniq
}

set_difference () {
   sort $1 $2 $2 | uniq -u
}

set_symmetric_difference() {
   sort $1 $2 | uniq -u
}

Ответ 3

Используйте comm - он будет сравнивать два отсортированных файла построчно.

Краткий ответ на ваш вопрос

Эта команда вернет строки, уникальные для deleteNodes, но не строки в keepNodes.

comm -1 -3 <(sort keepNodes) <(sort deleteNodes)

Пример настройки

Давайте создадим файлы с именами keepNodes и deleteNodes и используем их в качестве несортированных входных данных для команды comm.

$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)

По умолчанию при запуске comm без аргументов выводятся 3 столбца с таким макетом:

lines_unique_to_FILE1
    lines_unique_to_FILE2
        lines_which_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 без предварительной сортировки файла, то он корректно завершается с сообщением о том, какой файл не отсортирован.

comm: file 1 is not in sorted order

Ответ 4

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

Ответ 5

Возможно, вам нужен лучший способ сделать это в postgres, я могу в значительной степени поспорить, что вы не найдете более быстрый способ сделать это, используя плоские файлы. Вы должны иметь возможность сделать простое внутреннее соединение и предположить, что оба идентификатора cols индексируются, которые должны быть очень быстрыми.

Ответ 6

Таким образом, это немного отличается от других ответов. Я не могу сказать, что компилятор C++ является в точности "инструментом CLI для Linux", но работает с 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 BA (не AB, так как B - nodes_to_keep), и полученная разница будет напечатана в stdout.

Обратите внимание, что я упустил несколько рекомендаций C++, чтобы сделать код проще.

Можно было бы сделать много дополнительных оптимизаций скорости (за счет увеличения памяти). mmap также будет особенно полезен для больших наборов данных, но это сделает код гораздо более сложным.

Поскольку вы упомянули, что наборы данных велики, я подумал, что одновременное чтение по nodes_to_delete строки может быть хорошей идеей для уменьшения потребления памяти. Подход, использованный в приведенном выше коде, не особенно эффективен, если в вашем 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