78

我有两个文件A-nodes_to_deleteB- nodes_to_keep。每个文件都有多行带有数字 id 的行。

我想拥有在nodes_to_delete但不在的数字ID列表nodes_to_keep,例如替代文字

在 PostgreSQL 数据库中执行此操作非常慢。使用 Linux CLI 工具在 bash 中执行此操作的任何巧妙方法?

更新:这似乎是一项 Pythonic 工作,但文件非常非常大。uniq我已经使用和sort一些集合论技术解决了一些类似的问题。这比数据库等价物快大约两到三个数量级。

4

7 回答 7

116

comm命令就是这样做的。

于 2010-03-24T16:45:38.510 回答
52

几个月前,有人向我展示了如何在 sh 中做到这一点,然后我有一段时间找不到它......在寻找时我偶然发现了你的问题。这里是 :

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

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

set_symmetric_difference() {
   sort $1 $2 | uniq -u
}
于 2012-10-23T19:57:24.183 回答
11

使用comm- 它将逐行比较两个排序的文件。

对您的问题的简短回答

此命令将返回对 deleteNodes 唯一的行,而不是在 keepNodes 中。

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

示例设置

让我们创建名为keepNodesand的文件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

抑制列输出

使用 -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

于 2017-01-18T01:33:38.873 回答
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
于 2017-02-13T05:42:58.123 回答
1

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

于 2010-03-24T16:50:15.850 回答
1

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

grep -Fvx -f B A

参数:

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

如果 B 中的模式与 A 中的行不匹配,则命令输出该行,否则什么也不输出。

此解决方案的一个不错的功能是可以使其与多列文件(for A)一起使用,communiq -u解决方案需要一个列文件。

于 2021-03-03T08:58:59.023 回答
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 Anot A B,since Bis 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
于 2019-06-02T17:22:32.543 回答