awk:用另一个文件过滤文件的更优雅方式

时间:2015-10-22 10:29:08

标签: bash parsing optimization awk bigdata

我最近接近了非常快的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有时间和耐心花在这个任务上。 / p>

7 个答案:

答案 0 :(得分: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脚本。)

答案 1 :(得分: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";
    }
}

答案 2 :(得分: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

答案 3 :(得分:1)

在Vim中,找到模式实际上是单行的:

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

其中Q6GZX4;是您要查找的模式,以匹配序列字符。

以上基本上都会这样做:

  1. 在开头(AC)搜索^后面跟Q6GZX4;
  2. 的行。
  3. 关注across multiple lines\_.\{-})到以SQ\nSQ)开头的行。
  4. 然后按照下一行忽略当前(\_.\{-}\n)。
  5. 中的内容
  6. 现在开始选择主pattern\zs),基本上是across multiple lines\_.\{-})所有内容,直到(\ze//如果找到模式。
  7. 然后执行正常的Vim命令(norm),选择模式(gn)并将其拖入x寄存器("xy)。
  8. 您现在可以打印注册表(echo @x)或从中删除空格字符。
  9. 这可以扩展到Ex编辑器脚本,如下所示(例如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
    

    上述示例可以进一步扩展到多个文件或匹配项。

答案 4 :(得分: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;

更新另一个可能&#34;优雅&#34;:

3)awk pattern{action}样式已经是if / then的一种形式,所以你可以避免很多你的外部if / then嵌套:

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

答案 5 :(得分: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版本的速度要快一些。

答案 6 :(得分: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

不是很短,也许更通用一点。重要的部分是从行

中提取用作过滤器的值