【问题标题】:Filter column with awk and regexp使用 awk 和 regexp 过滤列
【发布时间】:2013-09-28 12:29:48
【问题描述】:

我有一个非常简单的问题。我有一个包含几列的文件,我想使用 awk 过滤它们。

所以感兴趣的列是第 6 列,我想找到包含的每个字符串:

  • 从 1 到 100 的数字开始
  • 之后是一个“S”或一个“M”
  • 又是一个从 1 到 100 的数字
  • 之后是一个“S”或一个“M”

所以每个例子:20S50M 是可以的

我试过了:

awk '{ if($6 == '/[1-100][S|M][1-100][S|M]/') print} file.txt

但它不起作用......我做错了什么?

【问题讨论】:

    标签: regex awk


    【解决方案1】:

    这应该可以解决问题:

    awk '$6~/^(([1-9]|[1-9][0-9]|100)[SM]){2}$/' file
    

    正则解释:

    ^                        # Match the start of the string
    (([1-9]|[1-9][0-9]|100)  # Match a single digit 1-9 or double digit 10-99 or 100
    [SM]                     # Character class matching the character S or M
    ){2}                     # Repeat everything in the parens twice
    $                        # Match the end of the string
    

    你的陈述有很多问题:

    awk '{ if($6 == '/[1-100][S|M][1-100][S|M]/') print} file.txt
    
    • == 是字符串比较运算符。正则表达式比较运算符是~
    • 您没有引用正则表达式字符串 (您永远不会在脚本本身旁边的 awk 中用单引号引用任何内容)并且您的脚本缺少最后的 (合法) 单引号。
    • [0-9]digit 字符的字符类,它不是数字范围。这意味着匹配 0,1,2,3,4,5,6,7,8,9 类中的任何字符而不是范围内的任何数值,因此 [1-100] 不是数字范围 1 - 100 中数字的正则表达式,它将匹配 1 或 0。
    • [SM] 等价于(S|M) 你尝试过的[S|M](S|\||M) 相同。您不需要在字符类中使用 OR 运算符。

    Awk 使用以下结构condition{action}。如果条件为 True,则为正在读取的当前记录执行以下块 {} 中的操作。我的解决方案中的条件是$6~/^(([1-9]|[1-9][0-9]|100)[SM]){2}$/,可以将其读取为第六列是否与正则表达式匹配,如果为True,则打印该行,因为如果您没有得到任何操作,则awk将默认执行{print $0}

    【讨论】:

    • 非常感谢,太好了!我只有最后一个问题。我想将此添加到 bash 脚本中,但输出为空。当我在 shell 中尝试该命令时,它运行良好。我在管道中使用 awk 命令和另一个程序的输出。命令 | awk '$6~/^(|[0-3][ID]){2}(([7-9]|[1-9][0-9]|100)[SM])(|[0- 3][ID]){2}(([7-9]|[1-9][0-9]|100)[SM])(|[0-3][ID]){2}$/ ' > out.txt
    • 绝对没有理由说明 awk 脚本在 shell 脚本和命令行中的行为会有所不同(假设两者都使用相同的 shell)。您的 shell 脚本中可能有一个错误。更新您的问题,以显示您在命令行上所做的操作的副本/粘贴,以及包含您的 shell 脚本的 shell 脚本和内容,以便我们帮助您识别问题。
    • +1 表示“正则解释”的概念。我知道这是线程死灵术,但此刻我深陷正则表达式漏洞,这让我微笑。 :)
    【解决方案2】:

    正则表达式无法检查数值。 “从 1 到 100 的数字”超出了正则表达式的功能。您可以做的是检查“1-3 位数”。

    你想要这样的东西

    /\d{1,3}[SM]\d{1,3}[SM]/
    

    请注意,字符类[SM] 没有! 交替字符。仅当您将其编写为 (S|M) 时才需要它。

    【讨论】:

    • "A number from 1 to 100" is outside what regexes can do 作为单个字符类你不能,使用正则表达式你当然可以。
    • 您所做的不是检查数值。您的答案会查找 1 位数字、2 位数字或文字 100。这不是检查数值。它只是伪造它。
    • 我的答案使用正则表达式来验证 1 - 100 范围内的数字。我在评论中明确指出,它不能用单个字符类来实现,并解释了字符类和数字范围之间的区别在我的回答中。您的解决方案没有锚定,允许 0 值和超过 100 的值,也不与 6 字段进行比较。
    【解决方案3】:

    我会将正则表达式检查和数字验证作为不同的步骤进行。此代码适用于 GNU awk:

    $ cat data
    a b c d e 132x123y
    a b c d e 123S12M
    a b c d e 12S23M
    a b c d e 12S23Mx
    

    我们希望只有第 3 行通过验证

    $ gawk '
        match($6, /^([[:digit:]]{1,3})[SM]([[:digit:]]{1,3})[SM]$/, m) && 
        1 <= m[1] && m[1] <= 100 && 
        1 <= m[2] && m[2] <= 100 {
            print
        }
    ' data
    a b c d e 12S23M
    

    为了可维护性,您可以将其封装到一个函数中:

    gawk '
        function validate6() {
            return( match($6, /^([[:digit:]]{1,3})[SM]([[:digit:]]{1,3})[SM]$/, m) && 
                    1<=m[1] && m[1]<=100 && 
                    1<=m[2] && m[2]<=100 );
        }
        validate6() {print}
    ' data
    

    【讨论】:

    • +1 是迄今为止唯一易于扩展的解决方案,如果 OP 在他说 number 时表示 positive integer 以外的意思!
    【解决方案4】:

    你发的脚本的写法:

    awk '{ if($6 == '/[1-100][S|M][1-100][S|M]/') print} file.txt
    

    在 awk 中,它会做你想做的事情:

    awk '$6 ~ /^(([1-9][0-9]?|100)[SM]){2}$/' file.txt
    

    发布一些示例输入和预期输出,以帮助我们为您提供更多帮助。

    【讨论】:

      【解决方案5】:

      我知道这个线程已经被回答,但我实际上有一个类似的问题(与查找“使用查询”的字符串有关)。我正在尝试将“S”、“M”、“I”、“=”、“X”、“H”等字符前面的所有整数相加,以通过配对端找到读取长度读取 CIGAR 字符串。

      我编写了一个 Python 脚本,它从 SAM/BAM 文件中获取 $6 列:

      import sys                      # getting standard input
      import re                       # regular expression module
      
      lines = sys.stdin.readlines()   # gets all CIGAR strings for each paired-end read
      total = 0
      read_id = 1                     # complements id from filter_1.txt
      
      # Get an int array of all the ints matching the pattern 101M, 1S, 70X, etc.
      # Example inputs and outputs: 
      # "49M1S" produces total=50
      # "10M757N40M" produces total=50
      
      for line in lines:
          all_ints = map(int, re.findall(r'(\d+)[SMI=XH]', line))
          for n in all_ints:
              total += n
          print(str(read_id)+ ' ' + str(total))
          read_id += 1
          total = 0
      

      read_id 的目的是将您正在经历的每次读取标记为“唯一”,以防您想获取 read_lengths 并将它们打印在 BAM 文件中的 awk 列旁边。

      我希望这会有所帮助,或者至少可以帮助下一个遇到类似问题的用户。 我咨询了https://stackoverflow.com/a/11339230 以供参考。

      【讨论】:

        【解决方案6】:

        试试这个:

        awk '$6 ~/^([1-9]|0[1-9]|[1-9][0-9]|100)+[S|M]+([1-9]|0[1-9]|[1-9][0-9]|100)+[S|M]$/' file.txt
        

        因为您没有确切说明第 6 列中的格式,所以上面的方法适用于列看起来像“03M05S”、“40S100M”或“3M5S”的地方;并排除所有其他内容。例如,它不会找到“03F05S”、“200M05S”、“03M005S”、“003M05S”或“003M005S”。

        如果您可以在 0-99 时将第 6 列中的数字保留为 2,或者在 100 时保留为 3 - 表示小于 10 时正好有一个前导零,否则没有前导零,那么这是一个更简单的匹配。您可以使用上述模式但排除单个数字(删除第一个 [1-9] 条件),例如

        awk '$6 ~/^(0[1-9]|[1-9][0-9]|100)+[S|M]+(0[1-9]|[1-9][0-9]|100)+[S|M]$/' file.txt
        

        【讨论】:

        • [S|M] 表示either of the letters "S", "|", or "M"。已经发布了一些简短的 RE,它们完成了 OP 似乎想要完成的工作。
        • Ed - 我正确回答了这个问题。我使用了您的答案(这是 Sudo_O 的副本)并且没有输出。这个问题不仅与正则表达式有关,更重要的是,它必须实际使用 awk 生成输出才能回答 NicoBxl 问题。
        • 我的答案不是@sudo_O 的副本(再读一遍),如果您没有输出,那么您的输入错误或者您的 awk 不支持 RE 间隔,在这种情况下获取更新的哦。您的答案不正确,因为它将匹配不是所需格式的字符串 - 在编写 RE 时排除相似但无效的字符串总是比简单地匹配所需的字符串更难得到正确。在输入文件中尝试使用价值 6 美元的 12|23| 甚至 12345678|98647329|
        • 我在 CentOS 6.4 上。抱歉,如果该操作系统上提供的工具不够新,无法支持您的回答。再次,我试图帮助提问者解决一个现实世界的问题。看起来像“专家”或获得 SO 积分不是我的目标。您是否创建了测试输入文件?我做到了。我运行了您的表达式,但在 CentOS 6.4 上使用 awk 不起作用 - 并非没有更多的努力让海报(您)帮助解决。
        • 再次,如果您无法让我的解决方案正常工作并且您确定您的输入是正确的,那么您使用的是旧的和/或损坏的 awk 版本,它甚至不符合 POSIX 标准。说真的,买一个新的,让自己在路上省去更多的麻烦。如果它是 gawk 的旧版本,那么现在您可以添加 --re-interval 选项。您发布了一个不起作用的解决方案。我指出了其中一个问题。不要这么防守。
        猜你喜欢
        • 2017-11-10
        • 1970-01-01
        • 2011-03-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-19
        • 1970-01-01
        相关资源
        最近更新 更多