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

时间:2010-03-24 16:39:01

标签: bash file-io set-difference

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

我希望获得nodes_to_delete但不在nodes_to_keep中的数字ID列表,例如alt text http://mathworld.wolfram.com/images/equations/SetDifference/Inline1.gif

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

更新:这似乎是一个Pythonic工作,但文件确实很大。我使用uniqsort和一些集合理论技术解决了一些类似的问题。这比数据库等价物快两到三个数量级。

7 个答案:

答案 0 :(得分:93)

comm命令可以做到这一点。

答案 1 :(得分:38)

有人在几个月前向我展示了如何做到这一点,然后我暂时找不到它......而且看着我偶然发现了你的问题。这是:

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

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

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

答案 2 :(得分: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

答案 3 :(得分:3)

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

答案 4 :(得分:1)

也许你需要一个更好的方法在postgres中做到这一点,我可以打赌你不会找到一个更快的方法来使用平面文件。你应该能够做一个简单的内连接,并假设两个id cols都被索引,应该非常快。

答案 5 :(得分: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 BB,所以运行nodes_to_keep(不是 mmap),结果差异将被打印到stdout

请注意,为了让代码更简单,我已经放弃了一些C ++最佳实践。

可以进行许多其他速度优化(以增加内存为代价)。 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 ):

const varArgs = f => {
  const go = args =>
    Object.defineProperties(
      arg => go(args.concat(arg)), {
        "runVarArgs": {get: function() {return f(args)}, enumerable: true},
        [TYPE]: {value: "VarArgs", enumerable: true}
      });

  return go([]);
};

const varLiftM = (chain, of) => f => { // TODO: replace recursion with a fold
  const go = (ms, g, i) =>
    i === ms.length
      ? of(g)
      : chain(ms[i]) (x => go(ms, g(x), i + 1));

  return varArgs(ms => go(ms, f, 0));
};

答案 6 :(得分:0)

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

grep -Fvx -f B A

参数:

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

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

此解决方案的一个很好的功能是可以使其与多列文件一起使用(对于 A),而 communiq -u 解决方案需要一个列文件。< /p>