我该怎么做?
File1 如下所示:
foo 1 scaf 3
bar 2 scaf 3.3
File2 如下所示:
foo 1 scaf 4.5
foo 1 boo 2.3
bar 2 scaf 1.00
我想要做的是找到在 File1 和 File2 中共同出现的行 当字段 1,2和3 相同时。
有办法吗?
答案 0 :(得分:12)
以下是正确的答案(就使用标准的 GNU coreutils 工具而言,而不是在 perl / awk 中编写自定义脚本它)。
$ join -j1 -o1.2,1.3,1.4,1.5,2.5 <(<file1 awk '{print $1"-"$2"-"$3" "$0}' | sort -k1,1) <(<file2 awk '{print $1"-"$2"-"$3" "$0}' | sort -k1,1)
bar 2 scaf 3.3 1.00
foo 1 scaf 3 4.5
好的,它是如何运作的:
首先,我们将使用一个可以合并两行的好工具join
。 join
有两个要求:
我们需要在输入文件中生成 keys ,为此我们使用简单的awk
脚本:
$ cat file1
foo 1 scaf 3
bar 2 scaf 3.3
$ <file1 awk '{print $1"-"$2"-"$3" "$0}'
foo-1-scaf foo 1 scaf 3
bar-2-scaf bar 2 scaf 3.3
你看,我们在第一栏添加了一些关键词,比如“ foo-1-scaf ”。
我们对 file2 做同样的事情。
BTW。 <file awk
只是写awk file
或cat file | awk
的奇特方式。
我们还应该通过键排序我们的文件,在我们的例子中这是第1列,所以我们添加
在命令的末尾| sort -k1,1
(排序按第1列到第1列的文字)
此时我们可以生成文件 file1.with.key 和 file2.with.key 并加入它们,
但假设这些文件很大,我们不想将它们复制到文件系统上。相反,我们可以使用名为bash
process substitution的东西来生成命名管道的输出(这将避免任何
不必要的中间文件创建)。欲了解更多信息,请阅读提供的链接。
我们的目标语法是:join <( some command ) <(some other command)
最后一件事是解释花哨的连接参数:-j1 -o1.2,1.3,1.4,1.5,2.5
-j1
- 在第1列(在两个文件中)按键加入 -o
- 仅输出字段1.2
(第一个文件字段2),1.3
(第一个文件第3列)等。
这样我们就加入了行,但join
只输出必要的列。
从这篇文章中吸取的教训应该是:
答案 1 :(得分:9)
广泛的实验加上对手册页的仔细审查表明你不能直接加入多个列 - 而且我所有的加入实例都很有趣,只使用一个连接列。
因此,任何解决方案都需要以某种方式将要连接的列连接成一列。标准连接命令还要求其输入处于正确的排序顺序 - 在GNU连接(info coreutils join)中有一条关于它并不总是需要排序数据的注释:
但是,作为GNU扩展,如果输入没有无法使用的行 排序顺序可以是任何将两个字段视为相等的顺序,如果和 只有当上述排序比较认为它们相等时才会发生。
使用给定文件执行此操作的一种可能方法是:
awk '{printf("%s:%s:%s %s %s %s %s\n", $1, $2, $3, $1, $2, $3, $4);}' file1 |
sort > sort1
awk '{printf("%s:%s:%s %s %s %s %s\n", $1, $2, $3, $1, $2, $3, $4);}' file2 |
sort > sort2
join -1 1 -2 1 -o 1.2,1.3,1.4,1.5,2.5 sort1 sort2
这会在开始时创建一个复合排序字段,使用':'分隔子字段,然后对文件进行排序 - 对于两个文件中的每一个。然后join命令连接两个复合字段,但只打印出非复合(非连接)字段。
输出结果为:
bar 2 scaf 3.3 1.00
foo 1 scaf 3 4.5
join -1 1 -2 1 -1 2 -2 2 -1 3 -2 3 -o 1.1,1.2,1.3,1.4,2.4 file1 file2
在MacOS X 10.6.3上,这给出了:
$ cat file1
foo 1 scaf 3
bar 2 scaf 3.3
$ cat file2
foo 1 scaf 4.5
foo 1 boo 2.3
bar 2 scaf 1.00
$ join -1 1 -2 1 -1 2 -2 2 -1 3 -2 3 -o 1.1,1.2,1.3,1.4,2.4 file1 file2
foo 1 scaf 3 4.5
bar 2 scaf 3.3 4.5
$
这是加入第3场(仅限) - 这不是想要的。
您需要确保输入文件的排序顺序正确。
答案 2 :(得分:4)
你可以试试这个
awk '{
o1=$1;o2=$2;o3=$3
$1=$2=$3="";gsub(" +","")
_[o1 FS o2 FS o3]=_[o1 FS o2 FS o3] FS $0
}
END{ for(i in _) print i,_[i] }' file1 file2
输出
$ ./shell.sh
foo 1 scaf 3 4.5
bar 2 scaf 3.3 1.00
foo 1 boo 2.3
如果你想省略不常见的行
awk 'FNR==NR{
s=""
for(i=4;i<=NF;i++){ s=s FS $i }
_[$1$2$3] = s
next
}
{
printf $1 FS $2 FS $3 FS
for(o=4;o<NF;o++){
printf $i" "
}
printf $NF FS _[$1$2$3]"\n"
} ' file2 file1
输出
$ ./shell.sh
foo 1 scaf 3 4.5
bar 2 scaf 3.3 1.00
答案 3 :(得分:3)
将前三个字段与awk结合起来可能最简单:
awk '{print $1 "_" $2 "_" $3 " " $4}' filename
然后您可以在“字段1”
上正常使用join
答案 4 :(得分:2)
怎么样:
cat file1 file2
| awk '{print $1" "$2" "$3}'
| sort
| uniq -c
| grep -v '^ *1 '
| awk '{print $2" "$3" "$4}'
这假设您不太担心字段之间的空白区域(换句话说,三个制表符和一个空格与空格和7个制表符没有区别)。当您谈论文本文件中的字段时,通常会出现这种情况。
它做的是输出两个文件,剥离最后一个字段(因为你在比较方面不关心那个)。它是相似的线相邻的排序然后将它们统一起来(用一个副本和一个计数替换每组相邻的相同线)。
然后摆脱所有那些具有一个计数(没有重复)的人,并打印出每个被剥离的计数。这为您提供了重复行的“键”,然后您可以使用另一个awk迭代来根据需要在文件中找到这些键。
如果两个相同的密钥只在一个文件中,因为文件在早期组合,将按预期工作。换句话说,如果您在file1
中有重复的密钥,但在file2
中却没有,那将是误报。
然后,我能想到的唯一真正的解决方案是检查file2
file1
中每一行的解决方案,尽管我确信其他人可能会提出更聪明的解决方案。
而且,对于那些喜欢一点受虐狂的人来说,这是上述不太有效的解决方案:
cat file1
| sed
-e 's/ [^ ]*$/ "/'
-e 's/ / */g'
-e 's/^/grep "^/'
-e 's/$/ file2 | awk "{print \\$1\\" \\"\\$2\\" \\"\\$3}"/'
>xx99
bash xx99
rm xx99
这个构造一个单独的脚本文件来完成工作。对于file1
中的每一行,它会在脚本中创建一行以在file2
中查找该行。如果您想了解它的工作原理,请在删除之前先查看xx99
。
而且,在这一篇中,空格确实重要,所以如果它不适用于file1
和file2
之间空格不同的行,请不要感到惊讶(尽管如此,与大多数空格一样)可怕的“脚本,可以通过管道中的另一个链接修复”。这里更多的是你可以为快速工作创造的可怕事物的例子。
这是不我会为生产质量代码做什么,但只要你在The Daily WTF发现它之前销毁它的所有证据,它就可以一次性使用: - )
答案 5 :(得分:2)
以下是在Perl中执行此操作的方法:
#!/usr/local/bin/perl
use warnings;
use strict;
open my $file1, "<", "file1" or die $!;
my %file1keys;
while (<$file1>) {
my @keys = split /\s+/, $_;
next unless @keys;
$file1keys{$keys[0]}{$keys[1]}{$keys[2]} = [$., $_];
}
close $file1 or die $!;
open my $file2, "<", "file2" or die $!;
while (<$file2>) {
my @keys = split /\s+/, $_;
next unless @keys;
if (my $found = $file1keys{$keys[0]}{$keys[1]}{$keys[2]}) {
print "Keys occur at file1:$found->[0] and file2:$..\n";
}
}
close $file2 or die $!;
答案 6 :(得分:2)
简单方法(没有 awk ,加入, sed 或 perl ),使用软件工具{{ 1}},cut
和grep
:
sort
输出(不打印不匹配的行):
cut -d ' ' -f1-3 File1 | grep -h -f - File1 File2 | sort -t ' ' -k 1,2g
工作原理......
bar 2 scaf 1.00
bar 2 scaf 3.3
foo 1 scaf 3
foo 1 scaf 4.5
列出了要搜索的所有行。cut
的{{1}}开关输入来自grep
的行,并搜索 File1 和 File2 。-f -
不是必需的,但可以让数据更易于阅读。 cut
的简明结果:
sort
输出:
datamash
如果 File1 很大并且有点多余,那么添加cut -d ' ' -f1-3 File1 | grep -h -f - File1 File2 | \
datamash -t ' ' -s -g1,2,3 collapse 4
可以加快速度:
bar 2 scaf 3.3,1.00
foo 1 scaf 3,4.5
答案 7 :(得分:1)
我曾经合作过的一位教授创建了一组perl脚本,可以在面向列的平面文本文件上执行大量类似数据库的操作。它被称为Fsdb。它绝对可以做到这一点,特别值得研究的是,这不仅仅是一次性需求(因此你不会经常编写自定义脚本)。
答案 8 :(得分:1)
与Jonathan Leffler提供的类似解决方案。
使用不同的分隔符创建2个临时排序文件,这些分隔符在第一个字段中包含匹配的列。 然后在第一个字段上加入临时文件,并输出第二个字段。
main
答案 9 :(得分:1)
使用datamash
&#39> 崩溃操作,加上一些整容sort
和tr
:
cat File* | datamash -t ' ' -s -g1,2,3 collapse 4 | sort -g -k2 | tr ',' ' '
输出(公共线有第5个字段,不常见的线没有):
foo 1 boo 2.3
foo 1 scaf 3 4.5
bar 2 scaf 3.3 1.00
答案 10 :(得分:0)
OP 没有显示预期的输出,所以 idk 是否正是所需的输出,但这是解决问题的方法:
$ awk '
{ key=$1 FS $2 FS $3 }
NR==FNR { val[key]=$4; next }
key in val {print $0, val[key] }
' file1 file2
foo 1 scaf 4.5 3
bar 2 scaf 1.00 3.3