【问题标题】:Extract multiple independent regex matches per line每行提取多个独立的正则表达式匹配
【发布时间】:2017-11-20 15:32:41
【问题描述】:

对于下面的文件,我想提取“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 这里的答案非常接近它,但是每当我尝试使用 match(...) 时,我都会收到“意外令牌附近的语法错误”消息。

期待您的解决方案!

谢谢,

菲利克斯

【问题讨论】:

  • 轻松使用GAWK
  • 您应该显示出现错误的代码——我们可能很容易解决这个问题。
  • 你会在一行中获得 3 批 XC:Z: 和 2 批 XM:Z: 吗?你能有一种模式而没有另一种吗?所需的输出究竟是什么——应该保留前缀吗?即使在单个输入行中有 2 个或更多匹配项,您是否希望输出中的每个模式都有一行,因此输出中的总行数可能大于输入中的行数。这并不难;这只是一个确定你想要做什么的问题。生成带有示例输出数据的 MCVE (minimal reproducible example) 也有帮助(显示的输入很好)。
  • @JonathanLeffler 我希望每行每个字符串(XC:Z: 和 XM:Z:) 恰好出现一次,感谢您提出的澄清问题。我同意在提供的两个示例之上的完整输出将更接近真实的 MCVE,抱歉没有添加它!
  • 为什么不现在添加呢? wrt 您的语法错误 - 您正在运行旧的、损坏的 awk,或者您正在从命令行调用 awk,而 bash 正在解释 !。如果不了解更多关于您的环境、您正在执行的内容以及您的预期输出等信息,我们将无法为您提供太多帮助。

标签: regex bash awk sed


【解决方案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

【讨论】:

  • 感谢@SLePort,这对我来说是应该的。我知道这是假设 XC:Z: 总是在 'XM:Z:' 之前,对吗?
  • 我进行了编辑以涵盖XM:ZXC:Z 之前的情况。
【解决方案2】:

以下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

【讨论】:

  • 感谢 RavinderSingh13,我喜欢您的回答,并希望能更好地理解它。你能解释一下 a,b 和 num,num1 取的是哪些值吗?我假设 a 和 b 是在 split 命令中创建的数组,对吗?
【解决方案3】:

如果我们不知道 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

【讨论】:

  • 这段代码做了应该做的事情,解释非常有用,谢谢!面对您令人难以置信的 sed 技能,我感到敬畏,非常强大!
【解决方案4】:

另一个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

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

【讨论】:

  • 嗨@karakfa,真的很喜欢你的解决方案1,似乎在我的文件上运行良好,谢谢。我不清楚三件事。首先,我们不应该在 for 循环内而不是在它之前重置 c 和 p (c=p="") 吗?第二,你为什么用“else if”而不是“if”?第三点是:'^'-符号通常不代表行首吗?如果是这样,尽管它之前总是至少有一列(“Mouse_10”),但它在这里如何工作? (#3 是我懒得去谷歌,如果你没有动力向我解释正则表达式,请随意忽略 - 但 #1 和 #2 会很好解决!)
  • 添加了解释。 if else 更好,因为它一次只能匹配一个模式,如果匹配上一个模式,则无需检查另一个模式。如果此解决方案解决了您的问题,您将需要投票和/或选择作为答案。
【解决方案5】:

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

match(s, ere)

返回位置,以字符为单位,从 1 开始编号,在 出现扩展正则表达式 ere 的字符串 s,如果出现则为零 它根本不会发生。 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 函数进行字符串操作(参考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 解决方案更简洁。

【讨论】:

    猜你喜欢
    • 2016-02-02
    • 2011-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多