我有两个大文件(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
最好的方法是什么?
答案 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