如何减少AWK中正则表达式的贪婪性?


14

我想在中进行非贪心模式(正则表达式)匹配awk。这是一个例子:

echo "@article{gjn, Author =   {Grzegorz J. Nalepa}, " | awk '{ sub(/@.*,/,""); print }'

是否可以编写选择较短字符串的正则表达式?

@article{gjn,

而不是这个长字符串?:

@article{gjn, Author =   {Grzegorz J. Nalepa},

我想得到这个结果:

 Author =   {Grzegorz J. Nalepa},



我有另一个例子:

echo“ 文章{gjn,作者= {Grzegorz J. Nalepa},” | awk'{sub(/  [^,] *,/,“”); 打印}'
      ↑↑^^^^^^

请注意,我在输入字符串和正则表达式的第一个位置将@字符更改为逗号(,)字符(并且也更改.*[^,]*)。是否可以编写选择较短字符串的正则表达式?

, Author =   {Grzegorz J. Nalepa},

而不是更长的字符串?:

,article{gjn, Author =   {Grzegorz J. Nalepa},

我想得到这个结果:

,article{gjn

4
就像正则表达式不足以进行健壮的HTML解析一样,它们可能无法进行这种上下文相关的语法解析。但是,如果您的输入集受到相当多的限制且格式正确,则只要声明限制条件,您就可以摆脱正则表达式。例如,你可以寻找Author下一个逗号和空格,然后空格,然后=跟空格,然后{其次是任何非}其次},尽管这需要(除其他事项外),你不能嵌套{}的内部= { ... }部分。
2012年

@ jw013,谢谢您的解释。但是,我将等待其他用户的建议。
nowy1 2012年

Answers:


18

如果要选择@,然后选择第一个,,则需要将其指定为@[^,]*,

@后跟任意数量的(*非逗号(株)[^,]),然后用逗号(,)。

这种方法相当于@.*?,,但不适用于类似的功能@.*?string,因为后面的内容不只是单个字符。否定一个字符很容易,但是在正则表达式中否定字符串则要困难得多

另一种方法是对输入进行预处理,以将输入替换或添加string在输入中不会出现的字符之前:

gsub(/string/, "\1&") # pre-process
gsub(/@[^\1]*\1string/, "")
gsub(/\1/, "") # revert the pre-processing

如果不能保证输入中不包含替换字符(\1如上),一种方法是使用转义机制:

gsub(/\1/, "\1\3") # use \1 as the escape character and escape itself as \1\3
                   # in case it's present in the input
gsub(/\2/, "\1\4") # use \2 as our maker character and escape it
                   # as \1\4 in case it's present in the input
gsub(/string/, "\2&") # mark the "string" occurrences

gsub(/@[^\2]*\2string/, "")

# then roll back the marking and escaping
gsub(/\2/, "")
gsub(/\1\4/, "\2")
gsub(/\1\3/, "\1")

这适用于fixed,string但不适用于任意正则表达式,例如@.*?foo.bar


非常感谢您的良好答复。在我的编辑中,我问了另一个示例(请参阅我的编辑)。
nowy1 2012年

6

已经有好几个不错的答案,它们为awk无法进行非贪婪匹配提供了解决方法,因此,我提供了一些使用Perl兼容正则表达式(PCRE)的替代方法的信息。请注意,awk可以perl使用-n命令行选项轻松地重新实现大多数简单的“匹配和打印” 脚本,并且可以使用a2p Awk到Perl 转换器将更复杂的脚本转换。

Perl有一个非贪婪的运算符,可以在Perl脚本和任何使用PCRE的东西中使用。例如,也在GNU grep的-P选项中实现。

PCRE Perl的正则表达式不同,但是非常接近。对于许多程序而言,它是正则表达式库的流行选择,因为它非常快,并且Perl对扩展正则表达式的增强非常有用。

perlre(1)手册页中:

   By default, a quantified subpattern is "greedy", that is, it will match
   as many times as possible (given a particular starting location) while
   still allowing the rest of the pattern to match.  If you want it to
   match the minimum number of times possible, follow the quantifier with
   a "?".  Note that the meanings don't change, just the "greediness":

       *?        Match 0 or more times, not greedily
       +?        Match 1 or more times, not greedily
       ??        Match 0 or 1 time, not greedily
       {n}?      Match exactly n times, not greedily (redundant)
       {n,}?     Match at least n times, not greedily
       {n,m}?    Match at least n but not more than m times, not greedily

3

这是旧文章,但以下信息可能对其他人有用。

显然,有一种方法可以在awk中执行非贪婪的RE匹配。基本思想是使用match(string,RE)函数,并逐渐减小字符串的大小,直到匹配失败为止,例如(unested):

if (match(string, RE)) {
    rstart = RSTART
    for (i=RLENGTH; i>=1; i--)
        if (!(match(substr(string,1,rstart+i-1), RE))) break;
    # At this point, the non-greedy match will start at rstart
    #  for a length of i+1
}

2

对于一般表达式,可以将其用作非贪婪匹配:

function smatch(s, r) {
    if (match(s, r)) {
        m = RSTART
        do {
            n = RLENGTH
        } while (match(substr(s, m, n - 1), r))
        RSTART = m
        RLENGTH = n
        return RSTART
    } else return 0
}

我正在根据@JimMellander的答案使用它。smatch的行为类似于match,返回:

s 正则表达式r发生的位置; 如果不存在,则返回0。变量RSTARTRLENGTH被设置为匹配字符串的位置和长度。


1

awk中没有办法进行非贪婪匹配。不过,您也许可以得到所需的输出。sch的建议适用于该行。如果您不能依靠逗号,但是“作者”始终是您想要的内容的开始,则可以这样做:

awk '{ sub(/@.*Author/,"Author"); print }'

如果“作者”之前的字符数始终相同,则可以执行以下操作:

awk '{ sub(/@.{21}/,""); print }'

您只需要知道整个数据集的数据情况即可。


0

天无绝人之路。通过使用逗号作为分隔符,可以很容易地解决给定的问题。

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk -F, '{sub(/^[ \t]/, "", $2); print $2}'

当字段数变化时,通常需要更好的东西。在这种情况下,找到停用词通常会奏效,因为您可以使用它们从行中删除任何内容。在示例的上下文中,这就是我所说的停用词。

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'

0

我知道这是一个老帖子。但是这里只是按照要求使用awk作为OP:
A = @ article {gjn2010jucs,Author = {Grzegorz J. Nalepa},
echo $ A | awk'sub (/ @ [^,] * /,“”)'

输出:
,作者= {Grzegorz J. Nalepa},


1
该答案是错误的,大约有五个原因。
斯科特(Scott)

3
您能帮我了解问题在哪里吗?输出似乎与要求的一致。试图理解为什么答案正确/不正确。
VINAY NAIR
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.