Объединение 2 файлов на основе одного столбца

Я хотел бы объединить два файла. Я просмотрел предыдущие вопросы и ответы, но ни один из них не соответствовал моему желаемому результату.

У меня есть два файла, разделенных запятыми, разной длины, file1.csv а также file2.csv,

Мне нужно объединить эти файлы на основе их первого поля. Если первое поле из file1.csv присутствует в file2.csv, то соответствующая строка из file2.csv следует добавить к этому из file1.csv, Если 1-го поля нет, то строка из 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

3 ответа

Решение

Это должно сделать это:

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]}: мы сейчас во втором файле. Если 1-е поле текущей строки было также в 1-м файле, выведите текущую строку и строку из первого файла.
  • else{print $0,"no match"}: если 1-го поля не было в первом файле, выведите текущую строку и "нет совпадения"

Обратите внимание, что я прохожу file2.csv в качестве первого файла и file1.csv как второй. Это связано с тем, что один из двух файлов должен храниться в памяти, поэтому лучше хранить самый маленький из двух.

Это должно сделать (сокращено (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".

Другой вариант - использовать соединение:

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'

Exlanation

  • -t, устанавливает разделитель полей
  • -a 1 убедитесь, что напечатаны все негодные строки
  • -o auto устанавливает формат вывода
  • -e 'no match' заменяет отсутствующие поля ввода на "нет совпадения"
  • <(list) Оператор связывает выходные данные команд сортировки с именованными каналами, которые затем используются в качестве файлов для команды соединения (называемой "Подстановка процессов").
Другие вопросы по тегам