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


71

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

我想要包含nodes_to_delete但不包含的数字ID的列表nodes_to_keep,例如替代文字

在PostgreSQL数据库中执行此操作的速度过慢。使用Linux CLI工具在ba​​sh中进行操作的任何巧妙方法?

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


我很好奇即将出现的答案。我相信,Bash的安全性更高。如果您会说“在python中”或“在php中”,或者您遇到的机会会更好:)
Extraneon 2010年

我看到了标题,并且所有人都准备好打击UI不一致和不寻常的帮助论坛。当我阅读实际问题时,这让我感到失望。:(
aehiilrs

Answers:


111

通讯命令做到这一点。


10
并且,如果文件尚未排序,请sort首先。
Extraneon 2010年

2
+1令我感到愚蠢的伟大工具,我不知道。谢谢!
亚当·马坦

9
@只是不会在这里发动火焰战争,但您的评论只是无礼。
亚当·马坦

4
@亚当:具有讽刺意味的是,“通讯”的奥秘可以追溯到可以将/ bin和/ usr / bin的全部内容保存在脑海中的时候,而之前是所有这些花哨的perls,pythons和mysqls。在那些较简单的V7时代,您不得不使用所有工具,或者(加油!)用ed(1)编写自己的工具,在雪地里,双向上山,我们喜欢它!;)如果我以后再开始的话,我可能永远不会知道comm。
msw

4
@Adam Matan:对不起,粗鲁绝对不是我的意图。实际上,我发布的命令是学习大量有关该系统的好方法,并且我过去经常做类似的事情来启发自己。否则,例如join(1),对我来说仍然是未知的。
某人

43

几个月前,有人向我展示了如何做到这一点,然后我暂时找不到了……而我却偶然发现了您的问题。这里是 :

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

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

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

1
我认为这比公认的答案要好...comm并非在所有环境中都可用。
danwyand 2014年

3
那是对称差异,不是正常的设定差异。
Tgr 2015年

@Tgr非常确定这是正常的设置差异。
orip

@ wieczorek1990我不确定stdin的示例适用于不适用于comm的sort + uniq解决方案,但无论如何-对于comm和sort + uniq而言,这种方法通常都是成功的(显示Peteris Krumins的comm示例)差异)'cmd -23 <(排序文件1)<(排序文件2)'参见catonmat.net/blog/set-operations-in-unix-shell-simplified
orip

4
set_difference并且set_symmetric_difference不会总是正常工作-如果第一个输入文件中的行在该文件中不是唯一的,它们将删除该行。
2016年

9

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

抑制列输出

用-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
+1表示包含OP特定问题答案的正确示例(输出行deleteNodes不在中keepNodes),但如果突出显示正确的解决方案会更好:comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
约翰B,

4

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 cols都被索引了,这应该非常快。


从技术上讲您是正确的,并且explain支持您的主张,但是对于非常大(约数千万)的表,它根本不起作用。
亚当·马坦

1
是的,它不像排序的comm之类的东西会受到您的内存的限制,但是我认为,如果您有两个只有int id字段的表,那么您可以毫无困难地进入数以千万计的表。
黑暗城堡

从理论上讲这是正确的,但是由于某种原因它根本不起作用。
亚当·马坦

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不是 A B,因为Bnodes_to_keep),结果差异将被打印到stdout。

请注意,为了让代码更简单,我放弃了一些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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.