根据单列合并两个文件

根据单列合并两个文件

我想合并两个文件。我查看了之前的问题和答案,但没有一个符合我的期望。

我有两个以逗号分隔的文件,长度不同,file1.csv并且file2.csv

我需要根据这些文件的第一个字段合并它们。如果file1.csv中存在第一个字段file2.csv,则应将 中的相应行file2.csv附加到 的行之后。如果不存在第一个字段,则应打印file1.csv中的行并将其附加到 之后。file1.csvno 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}:NRFNR是特殊变量,表示当前行号和当前文件分别。当传递多个文件名时,只有在读取第一个文件时,这两个文件名才会相等。因此,这意味着“在读取第一个文件时,将每一行保存在一个以第一个字段为键的数组中,然后移动到下一行”。
  • 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)员将排序命令的输出连接到命名管道,然后将其用作连接命令的文件(称为进程替换)

相关内容