2015-10-22 44 views
2

我最近接近了令人难以置信的快awk,因为我需要解析非常大的文件。 我不得不解析这种投入......awk:更优雅的方式来过滤另一个文件

ID 001R_FRG3G    Reviewed;   256 AA. 
AC Q6GZX4; 
[...] 
SQ SEQUENCE 256 AA; 29735 MW; B4840739BF7D4121 CRC64; 
    MAFSAEDVLK EYDRRRRMEA LLLSLYYPND RKLLDYKEWS PPRVQVECPK APVEWNNPPS 
    EKGLIVGHFS GIKYKGEKAQ ASEVDVNKMC CWVSKFKDAM RRYQGIQTCK IPGKVLSDLD 
    AKIKAYNLTV EGVEGFVRYS RVTKQHVAAF LKELRHSKQY ENVNLIHYIL TDKRVDIQHL 
    EKDLVKDFKA LVESAHRMRQ GHMINVKYIL YQLLKKHGHG PDGPDILTVK TGSKGVLYDD 
    SFRKIYTDLG WKFTPL 
// 
ID 002L_FRG3G    Reviewed;   320 AA. 
AC Q6GZX3; 
[...] 
SQ SEQUENCE 320 AA; 34642 MW; 9E110808B6E328E0 CRC64; 
    MSIIGATRLQ NDKSDTYSAG PCYAGGCSAF TPRGTCGKDW DLGEQTCASG FCTSQPLCAR 
    IKKTQVCGLR YSSKGKDPLV SAEWDSRGAP YVRCTYDADL IDTQAQVDQF VSMFGESPSL 
    AERYCMRGVK NTAGELVSRV SSDADPAGGW CRKWYSAHRG PDQDAALGSF CIKNPGAADC 
    KCINRASDPV YQKVKTLHAY PDQCWYVPCA ADVGELKMGT QRDTPTNCPT QVCQIVFNML 
    DDGSVTMDDV KNTINCDFSK YVPPPPPPKP TPPTPPTPPT PPTPPTPPTP PTPRPVHNRK 
    VMFFVAGAVL VAILISTVRW 
// 
ID 004R_FRG3G    Reviewed;   60 AA. 
AC Q6GZX1; dog; 
[...] 
SQ SEQUENCE 60 AA; 6514 MW; 12F072778EE6DFE4 CRC64; 
    MNAKYDTDQG VGRMLFLGTI GLAVVVGGLM AYGYYYDGKT PSSGTSFHTA SPSFSSRYRY 

......有了这样的文件过滤它...

Q6GZX4 
dog 

...得到的输出是这样的:

Q6GZX4 MAFSAEDVLKEYDRRRRMEALLLSLYYPNDRKLLDYKEWSPPRVQVECPKAPVEWNNPPSEKGLIVGHFSGIKYKGEKAQASEVDVNKMCCWVSKFKDAMRRYQGIQTCKIPGKVLSDLDAKIKAYNLTVEGVEGFVRYSRVTKQHVAAFLKELRHSKQYENVNLIHYILTDKRVDIQHLEKDLVKDFKALVESAHRMRQGHMINVKYILYQLLKKHGHGPDGPDILTVKTGSKGVLYDDSFRKIYTDLGWKFTPL 256 
dog MNAKYDTDQGVGRMLFLGTIGLAVVVGGLMAYGYYYDGKTPSSGTSFHTASPSFSSRYRY 60 

要做到这一点,我想出了这个代码:

BEGIN{ 
    while(getline<"filterFile.txt">0)B[$1]; 
} 
{ 
    if ($1=="ID") 
     len=$4; 
    else{ 
     if ($1=="AC"){ 
      acc=0; 
      line = substr($0,6,length($0)-6); 
      split(line,A,"; "); 

      for (i in A){ 
       if (A[i] in B){ 
        acc=A[i]; 
       } 
      } 
      if (acc){ 
       printf acc"\t"; 
      } 
     } 
     if (acc){ 
      if(substr($0, 1, 5) == "  "){ 
       printf $1$2$3$4$5$6; 
      } 
      if ($1 == "//"){ 
       print "\t"len 
      } 
     } 
    } 
} 

但是,由于我已经看到了许多使用awk完成的类似任务的例子,我认为可能有一个更加优雅和高效的方法来实现它。但我无法真正掌握通常在互联网上发现的超级简洁的例子。 既然这是我的输入,我的输出和代码我认为这是理解awk优化在性能和编码风格方面更多的好机会,如果一些awk-guru有足够的时间和耐心来完成这个任务。

+2

这可能是在[代码审查(http://codereview.stackexchange.com/) – chthonicdaemon

回答

1

对于这样的任务,一个想法是通过awk或sed管道化第二个文件,以便动态创建一个新的awk脚本来解析大文件。作为一个例子:

控制文件(F1):

test 
dog 

数据(F2):

tree 5 
test 2 
nothing 
dog 1 

的想法开始:

sed 's/^\(.*\)$/\/\1\/ {print $2}/' f1 | awk -f - f2 

(其中-f -装置:从标准输入中读取awk脚本,而不是从命名文件中读取)。

+0

很抱歉,但我更好真的不明白你在那里做了什么。加上我的“f2”与 –

1

Perl来救援:

#!/usr/bin/perl 
use warnings; 
use strict; 

open my $FILTER, '<', 'filterFile.txt' or die $!; 
my %wanted;    # Hash of the wanted ids. 
chomp, $wanted{$_} = 1 for <$FILTER>; 

$/ = "//\n";    # Record separator. 
while (<>) { 
    my ($id_string) = /^ AC \s+ (.*) /mx; 
    my @ids = split /\s*;\s*/, $id_string; 

    if (my ($id) = grep $wanted{$_}, @ids) { 
     print "$id\t"; 
     my ($seq) = /^ SQ \s+ .* $ ((?s:.*)) /mx; 
     $seq =~ s/\s+//g; # Remove whitespace. 
     $seq =~ s=//$==; # Remove the final //. 
     print "$seq\t", length $seq, "\n"; 
    } 
} 
+0

真的不一样谢谢,但是你确定这个解决方案比awk更快吗?我需要解析一个> 10 GB的文件 –

+0

尝试对它进行基准测试。对我来说,它绝对更具可读性,因此可以维护。 – choroba

0

用不同的字段分隔符一个awk的解决方案(以这种方式,可以避免使用substrsplit):

BEGIN { 
    while (getline<"filterFile.txt">0) filter[$1] = 1; 
    FS = "[ \t;]+"; OFS = ""; ORS = ""; 
} 

{ 
    if (flag) { 
     if (len) 
      if ($1 == "//") { 
       print "\t" len "\n"; 
       flag = 0; len = 0; 
      } else { 
       $1 = $1; 
       print; 
      } 
     else if ($1 == "SQ") len = $3; 
    } else if ($1 == "AC") { 
     for (i = 1; ++i < NF;) 
      if (filter[$i]) { 
       flag = 1; 
       print $i "\t"; 
       break; 
      } 
    } 
} 

END { if (flag) print "\t" len } 

注:此代码不是设计得短而是要快。这就是为什么我没有试图去除嵌套的if/else条件,但我试图尽可能减少整个文件的全局测试次数。 但是,自从我的第一个版本和几个基准测试以来发生了一些变化之后,我必须承认choroba perl版本要快一点。

+0

非常感谢! –

1

可能不会比原来短得多,但多个awk脚本将使代码更简单。首先AWK生成关心的记录,第二提取信息,第三格式

$ awk 'NR==FNR{keys[$0];next} 
       {RS="//"; 
       for(k in keys) 
        if($0~k) 
        {print "key",k; print $0}}' keys file 
| awk '/key/{key=$2;f=0;;next} 
     /SQ/{f=1;print "\n\n"key,$3;next} 
      f{gsub(" ","");printf $0} 
     END{print}' 
| awk -vRS= -vOFS="\t" '{print $1,$3,$2}' 

将打印

Q6GZX4 MAFSAEDVLKEYDRRRRMEALLLSLYYPNDRKLLDYKEWSPPRVQVECPKAPVEWNNPPSEKGLIVGHFSGIKYKGEKAQASEVDVNKMCCWVSKFKDAMRRYQGIQTCKIPGKVLSDLDAKIKAYNLTVEGVEGFVRYSRVTKQHVAAFLKELRHSKQYENVNLIHYILTDKRVDIQHLEKDLVKDFKALVESAHRMRQGHMINVKYILYQLLKKHGHGPDGPDILTVKTGSKGVLYDDSFRKIYTDLGWKFTPL  256 
dog  MNAKYDTDQGVGRMLFLGTIGLAVVVGGLMAYGYYYDGKTPSSGTSFHTASPSFSSRYRY 60 
0
awk 'FNR == NR { aFilter[ $1 ";"] = $1; next } 
    /^AC/ { 
     if (String !~ /^$/) print Taken "\t" String "\t" Len 
     Taken = ""; String = "" 
     for (i = 2; i <= NF && Taken ~ /^$/; i++) { 
     if($i in aFilter) Taken = aFilter[ $i] 
     } 
     Take = Taken !~ /^$/ 
     next 
     } 
    Take && /^SQ/ { Len = $3; next } 
    Take && /^[[:blank:]]/ { 
     gsub(/[[:blank:]]*/, "") 
     String = String $0 
     } 
    END { if(String !~ /^$/) print Taken "\t" String "\t" Len } 
     ' filter.txt YourFile 

不是真的短,也许有点更通用。重的部分是提取充当过滤器从行值

1

在Vim,它实际上是一个班轮找到模式:

/^AC.\{-}Q6GZX4;\_.\{-}\nSQ\_.\{-}\n\zs\_.\{-}\ze\/\// 

其中Q6GZX4;是你的模式来查找以匹配序列字符。

上面基本上都会做的事:

  1. 在其后是Q6GZX4;开始(^)与AC行搜索。
  2. 关注across multiple lines\_.\{-})以SQ\nSQ)开头的行。
  3. 然后按照下一行忽略当前的内容(\_.\{-}\n)。
  4. 现在开始选择主pattern\zs)如果发现它基本上是一切across multiple lines\_.\{-}),直到(\ze)的//模式。
  5. 然后执行正常的Vim命令(norm),选择模式(gn)并将其抽入x寄存器("xy)。
  6. 您现在可以打印注册表(echo @x)或从中删除空格字符。

这可以扩展成如下实施例的编辑器脚本(例如cmd.ex):

let s="Q6GZX4" 
exec '/^AC.\{-}' . s . ';\_.\{-}\nSQ\_.\{-}\n\zs\_.\{-}\ze\/\//norm gn"xy' 
let @x=substitute(@x,'\W','','g') 
silent redi>>/dev/stdout 
echon s . " " . @x 
redi END 
q! 

然后,从命令行运行为:

$ ex inputfile < cmd.ex 
Q6GZX4 MAFSAEDVLKEYDRRRRMEALLLSLYYPNDRKLLDYKEWSPPRVQVECPKAPVEWNNPPSEKGLIVGHFSGIKYKGEKAQASEVDVNKMCCWVSKFKDAMRRYQGIQTCKIPGKVLSDLDAKIKAYNLTVEGVEGFVRYSRVTKQHVAAFLKELRHSKQYENVNLIHYILTDKRVDIQHLEKDLVKDFKALVESAHRMRQGHMINVKYILYQLLKKHGHGPDGPDILTVKTGSKGVLYDDSFRKIYTDLGWKFTPL 

上面的例子可以进一步扩展为多个文件或匹配。

1

你的代码看起来几乎没问题。保持简单,单传就是这样。

只有一对夫妇建议:

1)围绕拆分的业务太乱/脆。也许尝试这样说:

 acc=""; 
     n=split($0,A,"[; ]+"); 

     for (i=2;i<=n;++i){ 
      if (A[i] in B){ 
       acc=A[i]; 
       break; 
      } 
     } 

2)不要在第一个参数使用输入数据到你printf秒。你永远不知道什么时候的东西,看起来像printf的格式化功能可能会进来,真正把事情搞得一团糟:

printf "%s\t",acc"; 

printf "%s%s%s%s%s%s",$1,$2,$3,$4,$5,$6; 

多一个可能的“优雅”更新:

3)pattern{action}awk风格已经是一种形式如果/那么,这样就可以避免很多你的外在的的if/then嵌套:

$1="ID" {len=$4} 
$1="AC" { 
    acc=""; 
    ... 
    } 
acc { 
    if(substr($0, 1, 5) == "  "){ 
     ... 
    } 
+0

非常感谢! –

相关问题