我想合并两个文件。我查看了之前的问题和答案,但没有一个符合我的期望。
我有两个以逗号分隔的文件,长度不同,file1.csv
并且file2.csv
。
我需要根据这些文件的第一个字段合并它们。如果file1.csv
中存在第一个字段file2.csv
,则应将 中的相应行file2.csv
附加到 的行之后。如果不存在第一个字段,则应打印file1.csv
中的行并将其附加到 之后。file1.csv
no match
file1.csv
(4列):
Contig_Spider_Gland_98_1_1,>Contig_Spider_Gland_98_1_1 [1169 - 963] (REVERSE SENSE),MQGHRRKLATPRQRAPRKERQRALLLRLQWRIGLQPCSRRNKSLDRKNIYWRYLVEYGSWKGRTHISDV,C#
Contig_Spider_Gland_98_7_3,>Contig_Spider_Gland_98_17965_1 [90 - 278],MADVEKTSCCTETKECCKDETCCENGQGACHTGKEECKDTCHKKACGCKAGEDCKCSDGKCGC,CC#CC#CC#C#C#C#C#C#C#C#C#C#
file2.csv
(7 列):
Contig_Spider_Gland_98_1_1, SignalP-4.1, SIGNAL, 1, 22, 0.808, YES
Contig_Spider_Gland_98_8_2, SignalP-4.1, SIGNAL 1, 20, 0.877, YES
期望输出:
Contig_Spider_Gland_98_1_1,>Contig_Spider_Gland_98_1_1 [1169 - 963] (REVERSE SENSE),MQGHRRKLATPRQRAPRKERQRALLLRLQWRIGLQPCSRRNKSLDRKNIYWRYLVEYGSWKGRTHISDV,C#,Contig_Spider_Gland_98_1_1, SignalP-4.1, SIGNAL, 1, 22, 0.808, YES
Contig_Spider_Gland_98_7_3,>Contig_Spider_Gland_98_17965_1 [90 - 278],MADVEKTSCCTETKECCKDETCCENGQGACHTGKEECKDTCHKKACGCKAGEDCKCSDGKCGC,CC#CC#CC#C#C#C#C#C#C#C#C#C#,no match
答案1
这应该可以做到:
awk -F, -vOFS=, '(NR==FNR){a[$1]=$0; next}
{
if(a[$1]){print $0,a[$1]}
else{print $0,"no match"}
}' file2.csv file1.csv
解释
awk -F, -vOFS=,
:运行awk
,将输入(-F
)和输出(-vOFS=,
)字段分隔符设置为,
。(NR==FNR){a[$1]=$0; next}
:NR
和FNR
是特殊变量,表示当前行号和当前文件分别。当传递多个文件名时,只有在读取第一个文件时,这两个文件名才会相等。因此,这意味着“在读取第一个文件时,将每一行保存在一个以第一个字段为键的数组中,然后移动到下一行”。if(a[$1]){print $0,a[$1]}
:我们现在在第二个文件中。如果当前行的第一个字段也在第一个文件中,则打印当前行和第一个文件中的行。else{print $0,"no match"}
:如果第一个字段不在第一个文件中,则打印当前行和“无匹配”
请注意,我将传递file2.csv
第一个文件和file1.csv
第二个文件。这是因为两个文件中有一个需要存储在内存中,因此最好存储其中最小的一个。
答案2
应该这样做(由 terdon 缩短(R)并修复(TM)):
#!/usr/bin/perl
use strict;
use warnings;
@ARGV==2 || die;
open(my $file1, $ARGV[0]) || die("Could not open \"$ARGV[0]\": $!");
open(my $file2, $ARGV[1]) || die("Could not open \"$ARGV[1]\": $!");
$"=","; #" (this comment exists only to prevent syntax hilighting from breaking)
while(my $l1 = <$file1>) {
chomp($l1);
my @f1 = split(",", $l1);
if(my $l2 = <$file2>) {
chomp($l2);
my @f2 = split(",", $l2);
if($f1[0] eq $f2[0]) {
print("@f1,@f2\n");
}
else {
push(@f1, "no_match");
seek($file2, -length($l2), 1);
print("@f1\n");
}
}
else {
push(@f1, "no_match");
print("@f1\n");
}
}
close($file1);
close($file2);
exit;
由于文件已排序,“file1.csv”是“file2.csv”的超集,并且两个文件中都没有重复的行:
- 比较两个文件中的下一行;
- 如果“file1.csv”中行的第一个字段与“file2.csv”中行的第一个字段匹配,则将“file2.csv”中的行附加到“file1.csv”中的行(以逗号分隔)并打印生成的行;否则将“no_match”字段附加到“file1.csv”中的行,在“file2.csv”中后退一行并打印“file1.csv”中修改后的行;
- 如果“file2.csv”没有更多行,则将“no_match”字段附加到“file1.csv”中的行并打印“file1.csv”中修改后的行。
答案3
另一个选择是使用连接:
join -t, file1.csv file2.csv -a 1 -o auto -e 'no match'
如果输入文件尚未排序,您可以一次性完成:
join -t, <(sort file1.csv) <(sort file2.csv) -a 1 -o auto -e 'no match'
说明
-t,
设置字段分隔符-a 1
确保所有不成对的行也打印出来-o auto
设置输出格式-e 'no match'
将缺失的输入字段替换为“无匹配”- 操作
<(list)
员将排序命令的输出连接到命名管道,然后将其用作连接命令的文件(称为进程替换)