【问题标题】:How to remove rows that match one of several regex patterns?如何删除与几种正则表达式模式之一匹配的行?
【发布时间】:2015-07-16 17:56:24
【问题描述】:

我有一个制表符分隔的文本文件,并希望有效地删除满足以下任一条件的整行:

  • ALT 列中等于 . 的值
  • NA00001 列中的值以及在两个分隔符 |/ 之前和之后具有相同数字的后续列,例如0|01|12/2

下面是一个示例输入文件:

CHROM POS     ID        REF ALT    QUAL FILTER INFO                              FORMAT      NA00001        NA00002        NA00003
20     14370   rs6054257 G      A       29   PASS   NS=3;DP=14;AF=0.5;DB;H2           GT:GQ:DP:HQ 0|0:48:1:51,51 0|0:48:8:51,51 1/1:43:5:.,.
20     17330   .         T      A       3    q10    NS=3;DP=11;AF=0.017               GT:GQ:DP:HQ 0|0:49:3:58,50 0|1:3:5:65,3   0/0:41:3
20     1110696 rs6040355 A      G,T     67   PASS   NS=2;DP=10;AF=0.333,0.667;AA=T;DB GT:GQ:DP:HQ 1|2:21:6:23,27 2|1:2:0:18,2   2/2:35:4
20     1110696 rs6040360 A      .     67   PASS   NS=2;DP=10;AF=0.333,0.667;AA=T;DB GT:GQ:DP:HQ 1|2:21:6:23,27 2|1:2:0:18,2   2/2:35:4

示例输出文件为:

CHROM POS     ID        REF ALT    QUAL FILTER INFO                              FORMAT      NA00001        NA00002        NA00003
20     17330   .         T      A       3    q10    NS=3;DP=11;AF=0.017               GT:GQ:DP:HQ 0|0:49:3:58,50 0|1:3:5:65,3   0/0:41:3
20     1110696 rs6040355 A      G,T     67   PASS   NS=2;DP=10;AF=0.333,0.667;AA=T;DB GT:GQ:DP:HQ 1|2:21:6:23,27 2|1:2:0:18,2   2/2:35:4

【问题讨论】:

  • 你有没有尝试解决这个问题?
  • 鉴于列格式的数据awk 在这里可能会更容易使用。

标签: regex unix awk sed pattern-matching


【解决方案1】:

您的示例似乎没有包含任何符合“ALT 列中的值等于 .”标准的行,或者满足第二个条件的行标准(标题行除外)。因此,我在您的示例中添加了一些自己的行进行测试;我希望我已经理解你的标准。

第一个标准很容易通过测试特定字段来匹配,如果我们在 awk 脚本中使用类似 awk:$5 == "." {next} 的东西会跳过该行。仅使用正则表达式也很简单:^[^^I]*^I[^^I]*^I[^^I]*^I[^^I]*^I\.^I,其中^I 是制表符,匹配仅带有“.”的行。在第五个 (ALT) 字段中。

使用严格的正则表达式,您不能直接表示“[a delimiter] 之前和之后的相同数字”。您必须通过交替使用特定值的子表达式来做到这一点:0[|/]0|1[|/]1|2[|/]2... 但是只有 10 位数字,所以这并不是特别繁琐。因此,例如,您可以使用一个较长的 egrep 命令行来执行此过滤:

egrep -v '^[^^I]*^I[^^I]*^I[^^I]*^I[^^I]*^I\.^I|0[|/]0|1[|/]1|2[|/]2|3[|/]3|4[|/]4|5[|/]5|6[|/]6|7[|/]7|8[|/]8|9[|/]9' input-file

显然,这不是您想要定期手动输入的内容,也不适合维护。一个小的 awk 脚本更好:

#! /usr/bin/awk -f
# Skip lines with "." in the fifth (ALT) field
$5 == "." {next}
# Skip lines with the same digit before and after the delimiter in any field
/0[|/]0/ {next}
/1[|/]1/ {next}
/2[|/]2/ {next}
/3[|/]3/ {next}
/4[|/]4/ {next}
/5[|/]5/ {next}
/6[|/]6/ {next}
/7[|/]7/ {next}
/8[|/]8/ {next}
/9[|/]9/ {next}

# Copy all other lines to the output
{print}

为了便于阅读,我已将单个数字检查作为单独的 awk 语句。

使用扩展正则表达式 (ERE),您可以使用反向引用直接表示“分隔符前后的相同字符”。应该谨慎使用反向引用,因为它们会产生病态的性能特征;当然,您必须使用支持它们的语言,例如 perl。 POSIX awk 和 Gnu gawk 没有。这是处理第二个标准的 Perl 单行代码:

LINE: while (<STDIN>) { next LINE if /(\d)[|\/]\g1/; print }

这可能不是很好的 Perl - 我几乎从不使用该语言 - 但它在我的测试中有效。 (\d) 匹配并记住分隔符之前的数字,\g1 匹配记住的分隔符之后的数字。

【讨论】:

    【解决方案2】:
    perl -alnE '$F[4]  eq "."           and
                $F[9] =~ m!(\d)[|/]\1!  and
                $F[10] =~ m!(\d)[|/]\1! and
                say'
    

    更新:抱歉,OP 要求对方...

    perl -alnE 'say unless (
                   $f[4] eq "."            or 
                   ( $F[9]  =~ m!(\d)[|/]\1! and
                     $F[10] =~ m!(\d)[|/]\1! and
                     $F[11] =~ m!(\d)[|/]\1!
                   )
                )' 
    

    或同等的

    perl -ane 'next if ( $f[4] eq ".");
               next if ( $F[9]  =~ m!(\d)[|/]\1! and
                         $F[10] =~ m!(\d)[|/]\1! and
                         $F[11] =~ m!(\d)[|/]\1! );
               print '
    

    【讨论】:

    • 你的意思是$F[4] eq "."
    • 添加了一些示例输出
    • 第二个标准不是很清楚:remove is ($4="." OR ( $10=~ 1/1 AND $11 =~ 1/1))。对吗?
    • 对不起布拉德:我的错 - $F[4] eq "."
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-08
    • 1970-01-01
    • 2022-11-02
    相关资源
    最近更新 更多