每行提取多个独立的正则表达式匹配

时间:2017-11-20 15:32:42

标签: regex bash awk sed

对于下面的文件,我想提取" XC:Z:"之后的两个字符串。和" XM:Z:"。例如:

  • 第一行输出应为:" TGGTCGGCGCGT,GAGTCCGT"
  • 第二行输出应为:" GAAGCCGCTTCC,ACCGACGG"

该文件的原始版本比以下示例具有更多列和数百万行,但它应该为您提供这样的想法:

    MOUSE_10        XC:Z:TGGTCGGCGCGT       RG:Z:A  XM:Z:GAGTCCGT   ZP:i:33
    MOUSE_10        XC:Z:GAAGCCGCTTCC       NM:i:0  XM:Z:ACCGACGG   AS:i:16
    MOUSE_10        ZP:i:36 XC:Z:TCCCCGGGTACA       NM:i:0  XM:Z:GGGACGGG   ZP:i:28
    MOUSE_10        XC:Z:CAAATTTGGAAA       RG:Z:A  NM:i:1  XM:Z:GCAGATAG

此外,以下每个标准都是奖励,但如果您能够将其付诸实施则不是强制性的:

  • 使用标准的bash工具:awk,sed,grep等(没有GAWK,csvtools,...)
  • 假设我们不知道XC和XM出现的顺序(虽然我相当确定XC几乎是第一个,但我不确定如何检查)。但是,在输出中,如果可能的话,XC字符串应始终位于XM字符串之前。

来自awk extract multiple groups from each line的答案非常接近它,但每当我尝试使用匹配(...)时,我会在意外令牌附近出现"语法错误"消息。

期待您的解决方案!

谢谢,

菲利克斯

5 个答案:

答案 0 :(得分:1)

使用sed,您可以在XC:Z:XM:Z之后捕获非空格字符:

sed -n 's/.*XC:Z:\([^[:blank:]]*\).*XM:Z:\([^[:blank:]]*\).*/\1, \2/p;' file

您可以为反转值添加第二个s命令:

sed -n 's/.*XC:Z:\([^[:blank:]]*\).*XM:Z:\([^[:blank:]]*\).*/\1, \2/;s/.*XM:Z:\([^[:blank:]]*\).*XC:Z:\([^[:blank:]]*\).*/\1, \2/;p;' file

答案 1 :(得分:0)

遵循awk解决方案可能对您有帮助。

awk '
/XC:Z:/{
  match($0,/XC:[^ ]*/);
  num=split(substr($0,RSTART,RLENGTH),a,":");
  match($0,/XM:[^ ]*/);
  num1=split(substr($0,RSTART,RLENGTH),b,":");
  print a[num],b[num1]
}'   Input_file

输出如下。

TGGTCGGCGCGT GAGTCCGT
GAAGCCGCTTCC ACCGACGG
TCCCCGGGTACA GGGACGGG
CAAATTTGGAAA GCAGATAG

答案 2 :(得分:0)

另一个awk

$ awk '{c=p="";                               # need to reset c and p before each line
        for(i=1;i<=NF;i++)                    # for all fields in the line
          if($i~/^XC:Z:/) c=substr($i,6)      # check pattern from the start of field
          else if($i~/^XM:Z:/) p=substr($i,6) # if didn't match check other other pattern 
        if(c && p) print c,p}' file           # if both matched print

TGGTCGGCGCGT GAGTCCGT
GAAGCCGCTTCC ACCGACGG
TCCCCGGGTACA GGGACGGG
CAAATTTGGAAA GCAGATAG

如果同一行上有多个实例,则会打印最后一个匹配项。这是另一个略有不同的特征。

$ awk 'function s(x) {return ($i~x)?substr($i,6):""}
      {c=p="";
       for(i=1;i<=NF;i++) {
         c=c?c:s("^XC:Z:"); p=p?p:s("^XM:Z:");
         if(c && p) 
           {print c,p; next}}}' file

TGGTCGGCGCGT GAGTCCGT
GAAGCCGCTTCC ACCGACGG
TCCCCGGGTACA GGGACGGG
CAAATTTGGAAA GCAGATAG

这将在另一个匹配的第一个匹配之前打印重复匹配的最后一个。它们成对出现,将打印第一对。

答案 3 :(得分:0)

如果我们不知道XC和XM出现的顺序 你可以尝试这个sed

sed -E 'h;s/(XC:Z:.*XM:Z:)//;tA;x;s/(.*XM:Z:)([^[:blank:]]*)(.*XC:Z:)([^[:blank:]]*)(.*)/\4,\2/;b;:A;x;s/(.*XC:Z:)([^[:blank:]]*)(.*XM:Z:)([^[:blank:]]*)(.*)/\2,\4/' infile

解释:

sed -E '
h
# keep the line in the hold space
s/(XC:Z:.*XM:Z:)//;x;tA
# if XCZ come before XMZ, go to A but before everything restore the pattern space with x
s/(.*XM:Z:)([^[:blank:]]*)(.*XC:Z:)([^[:blank:]]*)(.*)/\4,\2/
# XMZ come before XCZ, get the interresting parts and reorder it
b
# It is all for this line
:A
s/(.*XC:Z:)([^[:blank:]]*)(.*XM:Z:)([^[:blank:]]*)(.*)/\2,\4/
# XCZ come before XMZ, get the interresting parts
' infile

答案 4 :(得分:0)

使用POSIX awk,您只能使用IEEE Std 1003.1-2008定义的字符串函数match(s,ere)

  

match(s, ere)

     

返回位置,以字符为单位,编号为1,in   扩展正则表达式发生的字符串,如果是,则为零   它根本不会发生。 RSTART应设置为起始位置   (与返回值相同),如果未找到匹配则为零;   RLENGTH应设置为匹配字符串的长度,如果不是则设置为-1   找到匹配。

您要匹配的模式是XM:Z:[^[:blank:]]*XC:Z:[^[:blank:]]*。但是,假设您没有任何包含PXM:Z:之类的字符串(即提前搜索到的字符串的额外非空白字符)。当在$0行中找到模式时,您只需要提取重要部分,这些部分将在5个字符后开始。

以下代码执行上述操作:

   awk '{match($0,/XM:Z:[^[:blank:]]*/);xm=substr($0,RSTART+5,RLENGTH-5)}
        {match($0,/XC:Z:[^[:blank:]]*/);xc=substr($0,RSTART+5,RLENGTH-5)}
        {print xc","xm}' <file>

如您所见,第一行提取XM,第二行XC,第三行用逗号分隔符","打印结果。

备注 - 此处做出以下假设:

  • 每行包含xmxc字符串
  • 不存在[^[:blank:]]X[CM]:Z:[^[:blank:]]*类型的字符串

如果您愿意使用gawk,那么您可以使用patsplit函数进行字符串操作(Ref。here)。您可以使用单个正则表达式/X[CM]:Z:[^[:blank:]]*/执行此操作。这样可以在一次调用中直接为您提供所请求的字符串,其中包括XM:Z:XM:C:部分。之后,您可以轻松地对它们进行排序并提取最后的部分。

以下几行在gawk

中完全相同
   gawk '{patsplit($0,a,/X[MC]:Z:[^[:blank:]]*/) }
         {xc=(a[1]~/^XC/)?a[1]:a[2]; xm=(a[1]~/^XC/)?a[2]:a[1]}
         {print substr(xc,5)","substr(xm,5)' <file>

尽管如此,我认为awk解决方案从对称的角度来看更清晰。