Баш, Линукс:Установить разницу между двумя текстовыми файлами
-
22-09-2019 - |
Вопрос
у меня есть два файла A
-nodes_to_delete
и B
-nodes_to_keep
.Каждый файл имеет множество строк с числовыми идентификаторами.
Я хочу иметь список числовых идентификаторов, которые находятся в nodes_to_delete
но НЕ в nodes_to_keep
, например
.
Делать это в базе данных 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