【问题标题】:bash, Linux: Set difference between two text filesbash,Linux:设置两个文本文件之间的差异
【发布时间】:2011-01-31 08:37:06
【问题描述】:

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

我想要在nodes_to_delete 中但不在nodes_to_keep 中的数字ID 列表,例如 .

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

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

【问题讨论】:

  • 我很好奇会有什么答案。我相信 Bash 更像是系统管理员。如果你会说“在 python 中”或“在 php 中”或者任何你的机会会更好:)
  • 我看到了标题,并准备抨击 UI 不一致和比你更神圣的帮助论坛。当我阅读实际问题时,这让我感到失望。 :(

标签: bash file-io set-difference


【解决方案1】:

comm 命令执行此操作。

【讨论】:

  • 如果文件还没有排序,先sort
  • +1 开明的,很棒的工具,我觉得自己不知道很愚蠢。谢谢!
  • @Just不会在这里开始一场激烈的战争,但你的评论很粗鲁。
  • @Adam:具有讽刺意味的是,“comm”的奥秘可以追溯到一个时代,在所有这些花哨的 perls 和蟒蛇和mysqls。回到那些更简单的 V7 时代,您必须使用所有工具或(喘气!)编写自己的工具,使用 ed(1),在雪地里,双向上坡,我们喜欢它! ;) 如果我晚一点开始,我可能永远不会知道 comm。
  • @Adam Matan:对不起,粗鲁绝对不是我的本意。事实上,我发布的命令是学习大量系统知识的好方法,我曾经做过类似的事情来启发自己。否则 e. G。 join(1) 对我来说仍然是未知的。
【解决方案2】:

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

【讨论】:

  • 您在技术上是正确的,explain 支持您的主张,但它根本不适用于非常大(约数千万)的表。
  • 是的,它会受到你的记忆的限制,不像排序的通信,但我认为如果你有两个只有一个 int id 字段的表,你可以毫无困难地进入数百万.
  • 理论上是对的,但由于某种原因根本行不通。
【解决方案3】:

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

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

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

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

【讨论】:

  • 我认为这比公认的答案要好...comm 并非在所有环境中都可用。
  • 这是对称差,不是正态集差。
  • @Tgr 很确定这是正常的设置差异。
  • @wieczorek1990 我不确定 stdin 的哪些示例适用于不适用于 comm 的 sort+uniq 解决方案,但无论如何 - 对于 comm 和 sort+uniq - 这种方法通常会获胜(显示 Peteris Krumins 的设置差异的通信示例)'cmd -23 catonmat.net/blog/set-operations-in-unix-shell-simplified
【解决方案4】:

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

您的问题的简短回答

此命令将返回 deleteNodes 独有的行,而不是 keepNodes 中的行。

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

示例设置

让我们创建名为keepNodesdeleteNodes 的文件,并将它们用作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

【讨论】:

  • +1 用于正确示例,其中包括对 OP 特定问题的答案(deleteNodes 中的输出行不在keepNodes 中),但如果突出显示正确的解决方案会更好:@ 987654333@.
【解决方案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

【讨论】:

    【解决方案6】:

    所以,这与其他答案略有不同。我不能说 C++ 编译器完全是一个“Linux CLI 工具”,但运行 g++ -O3 -march=native -o set_diff main.cppmain.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,因为Bnodes_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
    

    【讨论】:

      【解决方案7】:

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

      grep -Fvx -f B A
      

      参数:

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

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

      这个解决方案的一个很好的特点是它可以处理多列文件(对于A),而communiq -u 解决方案需要一个列文件。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-04-04
        • 2010-11-23
        • 1970-01-01
        • 2010-12-06
        • 1970-01-01
        • 2017-02-14
        • 2014-02-02
        • 1970-01-01
        相关资源
        最近更新 更多