6

我有两个大文件(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

4 回答 4

3

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

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

然后我们使用 comm 在第三个字段上获得公共值:

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,您可以使用临时文件。

于 2012-09-15T23:56:20.700 回答
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 解决方案将非常有效:

解析.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行。$3a
  • p[$3]如果是一个键,则打印file1行并删除该键(仅打印一次)。$3p
于 2012-09-16T00:35:36.207 回答
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从两个文件中提取它们。

于 2012-09-15T23:17:44.100 回答
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
于 2012-09-15T23:28:04.520 回答