【问题标题】:Grab certain pattern from string using bash使用 bash 从字符串中获取特定模式
【发布时间】:2011-12-20 12:40:56
【问题描述】:

这可能是问题的扩展: Incorporate variables into bash code line

我刚刚意识到,在我的文本中,行实际上是可变格式的。

2   118610455   P2_PM_2_5034    T   <DUP:TANDEM>    40  .   END=118610566;SVLEN=110;SVTYPE=TDUP;CIPOS=-100,55;CIEND=-56,100;IMPRECISE;DBVARID=esv7540;VALIDATED;VALMETHOD=CGH;SVMETHOD=RP 

1   859214  P2_M_061510_1_73    C   <DEL>   .   .   CIEND=-130,50;CIPOS=-57,93;END=860180;IMPRECISE;SVLEN=-966;SVTYPE=DEL;VALIDATED;DBVARID=esv10036;VALMETHOD=CGH;SVMETHOD=RD,RP

我需要的是

2 118610455 118610566
1 859214 860180

如上所示,"END=#" 可能在第 8 列的不同位置。所以基本上我需要先从第 8 列中找到“END=..”部分,然后 grep 数字。 所以这实际上是关于如何从字符串中 grep 特定模式(在这种情况下,模式是“END =”)

但是我该怎么做呢? 谢谢

【问题讨论】:

    标签: string grep design-patterns


    【解决方案1】:

    Grep:

    您可以使用grep-o 选项进行搜索:

    测试:

    [jaypal:~/Temp] grep -o "END=[0-9]\+;" file | tr -ds 'END=|;' ''
    118610566
    860180
    

    但是,如果您正在寻找一个完整的解决方案,那么使用awk 怎么样(对不起,我知道这不是您的要求。但这里有两个解决方案:

    Awk:

    如果您想要的第一个和第二个参数的位置不变,那么我们可以将每个值拆分到特定字段中,然后遍历每个值。一旦我们到达END 的字段,我们就会打印$1 和$4,然后打印END 旁边的列。

    awk -v FS="[ ;=]" '{for(i=1;i<=NF;i++) if ($i=="END") print $1,$4,$(i+1)}' file
    

    测试:

    [jaypal:~/Temp] cat file
    2   118610455   P2_PM_2_5034    T   <DUP:TANDEM>    40  .   END=118610566;SVLEN=110;SVTYPE=TDUP;CIPOS=-100,55;CIEND=-56,100;IMPRECISE;DBVARID=esv7540;VALIDATED;VALMETHOD=CGH;SVMETHOD=RP 
    1   859214  P2_M_061510_1_73    C   <DEL>   .   .   CIEND=-130,50;CIPOS=-57,93;END=860180;IMPRECISE;SVLEN=-966;SVTYPE=DEL;VALIDATED;DBVARID=esv10036;VALMETHOD=CGH;SVMETHOD=RD,RP
    
    [jaypal:~/Temp] awk -v FS="[ ;=]" '{for(i=1;i<=NF;i++) if ($i=="END") print $1,$4,$(i+1)}' file
    2 118610455 118610566
    1 859214 860180
    

    GNU AWK:

    如果您有gawk,那么它有一个名为gensub 的内置函数。这支持反向引用。因此,您还可以执行以下操作 -

    gawk '{print $1,$2,gensub(/.*\<END\>=(.[^;]*);.*/,"\\1",$0)}' file
    

    测试:

    [jaypal:~/Temp] gawk '{print $1,$2,gensub(/.*\<END\>=(.[^;]*);.*/,"\\1",$0)}' file
    2 118610455 118610566
    1 859214 860180
    

    【讨论】:

      【解决方案2】:

      使用 sed:

      $ cat input | sed -e 's/^\([0-9]\+\) \+\([0-9]\+\) .*\<END=\([0-9]\+\).*/\1 \2 \3/'
      

      【讨论】:

        【解决方案3】:

        您可以为此使用 perl 脚本,例如:

        pax> perl -ne '{
                 @arr=split;
                 if (@arr[7] =~ /^END=/) {
                     @arr[7] =~ s/^END=//;
                 } else {
                     @arr[7] =~ s/^.*;END=//;
                 }
                 @arr[7] =~ s/;.*$//;
                 printf "%s %s %s\n", @arr[0], @arr[1], @arr[7];
             }' <qq.in
        2 118610455 118610566
        1 859214 860180
        

        为了便于阅读,我已对该脚本进行了格式化,但您也可以轻松地使用单行:

        perl -ne '{@arr=split;if (@arr[7] =~ /^END=/) {@arr[7] =~ s/^END=//;} else {@arr[7] =~ s/^.*;END=//;} @arr[7] =~ s/;.*$//; printf "%s %s %s\n", @arr[0], @arr[1], @arr[7];}' <qq.in
        

        一旦您了解它,它的工作方式就很简单。 split 为您提供行中元素的数组,您只需稍微修改数字 7。

        如果它以END= 开头,就去掉那个位。否则,如果包括;END= 在内的所有内容,请摆脱。

        然后删除第一个 ; 之后的所有内容(在已经修改的版本中,在开头有 END=NN 位)。

        然后只需打印出三个所需的值。


        再想一想,还是把它简单一点可能会更好,比如:

        pax> perl -ne '{
                ($a,$b,$x,$x,$x,$x,$x,$c,$x) = split;
                $c = ";$c";
                $c =~ s/^.*;END=//;
                $c =~ s/;.*$//;
                print "$a $b $c\n";
            }' <qq.in
        

        或等效的单线:

        perl -ne '{($a,$b,$x,$x,$x,$x,$x,$c,$x)=split;$c=";$c";$c=~s/^.*;END=//;$c=~s/;.*$//;print "$a $b $c\n";}' <qq.in
        

        【讨论】:

          猜你喜欢
          • 2019-03-20
          • 2020-02-28
          • 1970-01-01
          • 2016-11-20
          • 2013-07-18
          • 2013-01-30
          • 2020-11-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多