使用awk和regexp过滤列

时间:2013-09-23 14:38:59

标签: regex awk

我有一个非常简单的问题。我有一个包含多个列的文件,我想使用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

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

6 个答案:

答案 0 :(得分:37)

这应该可以解决问题:

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

Regexplanation:

^                        # 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引用任何带引号的内容)并且你的脚本缺少最后的(合法)< / em>单引号。
  • [0-9]数字字符的字符类,它不是数字范围。这意味着匹配类0,1,2,3,4,5,6,7,8,9中的任何字符,而不是范围内的任何数值,因此[1-100]不是数字范围1 - 100中的数字的正则表达式,它将匹配1或0 。
  • [SM]相当于(S|M)您尝试的内容[S|M](S|\||M)相同。您不需要字符类中的OR运算符。

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

答案 1 :(得分:2)

我会将正则表达式检查和数字验证作为不同的步骤。此代码适用于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

答案 2 :(得分:1)

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

你想要这样的东西

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

请注意,字符类[SM]没有!替换字符。如果您将其写为(S|M),则只需要这样做。

答案 3 :(得分:1)

编写您发布的脚本的方法:

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

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

答案 4 :(得分:0)

试试这个:

  

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

答案 5 :(得分:0)

我知道该线程已经得到解答,但是实际上我有一个类似的问题(与查找“使用查询”的字符串有关)。我正在尝试对字符'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-ed列旁边。

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