bash,Linux:设置两个文本文件之间的差异

问题描述 投票:0回答:7

我有两个文件

A
-
nodes_to_delete
B
-
nodes_to_keep
。每个文件都有很多带有数字 ID 的行。

我想要在

nodes_to_delete
但不在
nodes_to_keep
中的数字ID列表,即
A\B

在 PostgreSQL 数据库中执行它非常慢。使用 Linux CLI 工具在 bash 中有什么巧妙的方法吗?

更新: 这似乎是一个 Pythonic 的工作,但文件真的非常大。我已经使用

uniq
sort
和一些集合论技术解决了一些类似的问题。这比数据库等效项快两到三个数量级。

bash file-io set-difference
7个回答
120
投票

comm 命令就是这样做的。


55
投票

使用

uniq -u
--unique
),它只打印非重复行:

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

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

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

使用

uniq -d
--repeated
),它只打印重复的行:

set_intersection () {
   sort $1 $2 | uniq -d
}

14
投票

使用

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 列布局:

$ comm FILE1 FILE2
lines_unique_to_FILE1
    lines_unique_to_FILE2
        lines_which_appear_in_both

使用上面的示例文件,运行不带参数的 comm。注意三列。

$ comm <(sort keepNodes) <(sort deleteNodes)
amber
    ann
        bob

抑制列输出

用 -N 抑制第 1、2 或 3 列;请注意,当列被隐藏时,空白会缩小。

$ 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


5
投票

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

1
投票

也许您需要一种更好的方法来在 postgres 中执行此操作,我敢打赌您不会找到使用平面文件来执行此操作的更快方法。你应该能够做一个简单的内部连接并假设两个 id 列都被索引应该非常快。


1
投票

另一个可移植的解决方案,也适用于 multisets,一个允许元素的多个实例的集合,是在单独的文件中使用 grep 和模式:

grep -Fvx -f B A

参数:

  • -f:包含模式列表的文件,逐行
  • -F:将模式视为字符串,而不是正则表达式
  • -x:匹配 A-nodes_to_delete 中的整行
  • -v:反转匹配(不匹配则匹配)

如果 B 中的模式与 A 中的一行不匹配,则命令输出该行,否则不输出任何内容。

这个解决方案的一个很好的特点是它可以使用多列文件(对于

A
),而
comm
uniq -u
解决方案需要一个列文件。


0
投票

所以,这与其他答案略有不同。我不能说 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
not
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
© www.soinside.com 2019 - 2024. All rights reserved.