文件的交叉点

时间:2012-09-15 22:54:13

标签: algorithm file unix intersection

我有两个大文件(27k行和450k行)。他们看起来有点像:

File1:
1 2 A 5
3 2 B 7
6 3 C 8
...

File2:
4 2 C 5
7 2 B 7
6 8 B 8
7 7 F 9
... 

我想要两个文件中的第三列所在的文件中的行(排除了包含A和F的行):

OUTPUT:
3 2 B 7
6 3 C 8
4 2 C 5
7 2 B 7
6 8 B 8

最好的方法是什么?

4 个答案:

答案 0 :(得分:3)

首先我们对第三个字段上的文件进行排序:

sort -k 3 file1 > file1.sorted
sort -k 3 file2 > file2.sorted

然后我们使用comm:

在第3个字段上获得常用值
comm -12 <(cut -d " " -f 3 file1.sorted | uniq) <(cut -d " " -f 3 file2.sorted | uniq) > common_values.field

现在我们可以在常用值上加入每个已排序的文件:

join -1 3 -o '1.1,1.2,1.3,1.4' file1.sorted common_values.field > file.joined
join -1 3 -o '1.1,1.2,1.3,1.4' file2.sorted common_values.field >> file.joined

输出格式化,因此我们获得与文件中​​使用的字段顺序相同的字段顺序。 使用的标准unix工具:sort,comm,cut,uniq,join。 <( )适用于bash,对于其他shell,您可以使用临时文件。

答案 1 :(得分:3)

这是一个使用grep,sed和cut的选项。

提取第3栏:

cut -d' ' -f3 file1 > f1c
cut -d' ' -f3 file2 > f2c

file1中找到匹配的行:

grep -nFf f2c f1c | cut -d: -f1 | sed 's/$/p/' | sed -n -f - file1  > out

file2中找到匹配的行:

grep -nFf f1c f2c | cut -d: -f1 | sed 's/$/p/' | sed -n -f - file2 >> out

输出:

3 2 B 7
6 3 C 8
4 2 C 5
7 2 B 7
6 8 B 8

更新

如果您有非对称数据文件且较小的数据文件适合内存,这种单程awk解决方案将非常有效:

parse.awk

FNR == NR {
  a[$3] = $0
  p[$3] = 1
  next
}  

a[$3]

p[$3] {
  print a[$3]
  delete p[$3]
}

像这样运行:

awk -f parse.awk file1 file2

file1是两者中较小者。

说明

  • FNR == NR块将file1读入两个哈希值。
  • 如果a[$3]file2中的密钥,则
  • $3会打印a行。
  • 如果p[$3]file1中的密钥,则
  • $3打印p行,并删除密钥(仅打印一次)。

答案 2 :(得分:2)

awk '{print $3}' file1 | sort | uniq > file1col3
awk '{print $3}' file2 | sort | uniq > file2col3
grep -Fx -f file1col3 file2col3 | awk '{print "\\w+ \\w+ " $1 " \\w+"}' > col3regexp
egrep -xh -f col3regexp file1 file2

抓取两个文件中所有唯一的列3,与它们相交(使用grep -F),打印一堆与所需列匹配的正则表达式,然后使用egrep从中提取它们这两个文件。

答案 3 :(得分:1)

首先从第三列获取公共值。然后过滤具有匹配的第三列的两个文件中的行。

如果列由单个字符分隔,则可以使用cut提取一列。对于可以由任意数量的空格分隔的列,请使用awk。获取公共列3值的一种方法是提取列,对它们进行排序并调用comm。使用bash / ksh / zsh进程替换:

comm -12 <(awk '{print $3}' file1 | sort -u) <(awk '{print $3}' file2 | sort -u)

现在将这些转换为grep个模式,然后过滤。

comm -12 <(awk '{print $3}' file1 | sort -u) <(awk '{print $3}' file2 | sort -u) |
sed -e 's/[][.\|?*+^$]/\\&/g' \
    -e 's/.*/^[^[:space]]+[[:space]]+[^[:space]]+[[:space]]+\1[[:space]]/' |
grep -E -f - file1 file2

上述方法应该可以很好地处理大文件。但是在500k行,你没有庞大的文件。这些文件应该适合内存,一个简单的Perl解决方案就可以了。加载两个文件,提取列值,打印匹配的列。

perl -n -e '
    @lines += 1;
    $c = (split)[2];
    $seen{$c}{$ARGV} = 1;
END {
    foreach (@lines) {
        $c = (split)[2];
        print if %{$seen{$c}} == 2;
    }
}' file1 file2