Баш, Линукс:Установить разницу между двумя текстовыми файлами

StackOverflow https://stackoverflow.com/questions/2509533

  •  22-09-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

А связь команда делает это.

Другие советы

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

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)

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

Давайте создадим файлы с именами 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

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

Итак, это немного отличается от других ответов.Я не могу сказать, что компилятор 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
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top