基于第三个文件中的两列生成2个文件

时间:2018-03-14 20:14:16

标签: perl unix awk grep

我正在尝试根据第三个文件中的信息准备两个输入文件。文件1用于sample1,文件2用于sample2。这两个文件都有带制表符分隔列的行。第一列包含唯一标识符,第二列包含信息。

档案1

>ENT01 xxxxxxxxxxxxxx
>ENT02 xyxyxyxyxyxy
>ENT03 ththththththt

..等等。同样,文件2包含

>ENG012 ggggggggggggg
>ENG098 ksksksksksks
>ENG234 wewewewewew

我有一个文件3,其中包含两列,每列对应于文件1和文件2中的标识符

>ENT01 >ENG78
>ENT02 >ENG098
>ENT02 >ENG012
>ENT02 >ENG234
>ENT03 >ENG012

等等。我想按照文件3中的顺序为文件1和文件2准备输入文件。如果在文件3(例如ENT02)中重复输入,我想重复该条目的信息。预期的产出是 对于文件1:

>ENT01 xxxxxxxxxx
>ENT02 xyxyxyxyxyxy
>ENT02 xyxyxyxyxyx
>ENT02 xyxyxyxyxyx
>ENT03 ththththththth

对于文件2

>ENG78 some info
>ENG098 ksksksksks
>ENG012 gggggggg
>ENG234 wewewewewew
>ENG012 gggggggg

文件1和文件2中的所有条目都是唯一的,但不在文件3中。此外,在任一列的file3中都有一些条目在文件1或文件2中不存在。我正在使用的当前逻辑是找到文件1和2中第1列的标识符与文件3中各自列的交集,将其存储为列表并使用该列表分别与File1和File 2进行比较,以生成文件1和文件1的输出。 2.我正在使用以下几行

awk 'FNR==NR{a[$1]=$0;next};{print a[$1]}' file1 intersectlist

grep -v -x -f idsnotfoundinfile1 file3

我无法获得正确的输出,因为我认为在某些时候它正在排序并且只打印出uniq值。有人可以帮我清楚解决这个问题。

2 个答案:

答案 0 :(得分:1)

您需要将前两个文件读取并记住到某个数据结构中,然后对于第三个文件,输出到2个新文件:

$ awk -F'\t' -v OFS='\t' '
      FNR == 1 {file_num++}
      file_num == 1 || file_num == 2 {data[file_num,$1] = $2; next}
      function value(str) {
          return str ? str : "some info"
      }
      {
          for (i=1; i<=2; i++) {
              print $i, value(data[i,$i]) > ARGV[i] ".new"
          }
      }
  ' file1 file2 file3


$ cat file1.new
>ENT01  xxxxxxxxxxxxxx
>ENT02  xyxyxyxyxyxy
>ENT02  xyxyxyxyxyxy
>ENT02  xyxyxyxyxyxy
>ENT03  ththththththt

$ cat file2.new
>ENG78  some info
>ENG098 ksksksksksks
>ENG012 ggggggggggggg
>ENG234 wewewewewew
>ENG012 ggggggggggggg

答案 1 :(得分:1)

首先需要读取文件1和2,以便您可以在文件3中找到带有标识符的行。由于这些文件中的标识符是唯一的,因此您可以为每个文件构建一个哈希值,并将标识符作为键。

然后逐行处理文件3,其中行上的每个标识符从相应文件的哈希值中检索其值,并将相应的行写入新文件1和2。

use warnings;
use strict;
use feature 'say';
use Path::Tiny;

my ($file1, $file2, $file3) = qw(File1.txt File2.txt File3.txt);

my ($fileout1, $fileout2) = map { $_ . 'new' } ($file1, $file2);

my %file1 = map { split } path($file1)->lines;
my %file2 = map { split } path($file2)->lines;

my ($ofh1, $ofh2) = map { path($_)->openw } ($fileout1, $fileout2);

open my $fh, '<', $file3 or die "Can't open $file3: $!";

while (<$fh>) {
    my ($f1, $f2) = split;

    say $ofh1 "$f1\t", $file1{$f1} // 'some info';   #/ see text
    say $ofh2 "$f2\t", $file2{$f2} // 'some info';
}
close $_ for $ofh1, $ofh2, $fh;

这将根据提供的输入文件片段生成正确的输出。

我在这里使用Path::Tiny是为了简洁。它的lines方法会返回所有行,而map的块中默认空格为split。由map返回的这种对的列表被分配给散列,其中每对连续的字符串形成键值对。

可以在一个语句中打开多个文件,Path::Tiny再次使用openw清除它。它的方法会抛出异常(die),因此我们也会得到错误检查。

如果在文件1/2中找不到文件3中的标识符,我会直截了当地使用问题中所述的'some info'但我希望有更全面的解决方案这种情况。然后应该更改简洁//以适应额外处理(或调用sub代替'some info'字符串)。

假设文件1和2在一行上总是有两个条目。

采用了一些快捷方式,例如将每个文件读入一行的哈希值。请根据需要扩展代码,并进行任何检查。

在这种情况下,$file1{$f1}undef,因此//(defined-or)运算符返回其右侧参数。 “正确”的方式是测试if (exist $file1{$f1}),但//也适用。