正则表达式递归:匹配两个字符的开头

时间:2014-10-26 21:12:19

标签: regex recursion

我正在尝试使用递归正则表达式来匹配类似bash的变量扩展。基本上,我应该能够匹配如下字符串:

${FOO=BAR}
${FOO=${BAR=BAZ}}

但也处理这样的输入:

${FOO=\${BAR=BAZ}}
${FOO={${BAR=BAZ}}

在第一种情况下,它应匹配,但不包括最终},第二种情况应完全匹配。这是因为我试图将双字符开头${与单字符结束}匹配。打开和关闭都应该能够逃脱。

我已经达到了PCRE正则表达式\$\{(?:[^{}]|(?R))*\}。但这并不能正确处理转义。如果我将其修改为(?:^|[^\\])(?:\\\\)*(\$\{(?:[^{}]|(?R))*\}),则只有最外面的转义符才能正确匹配。

这可以用正则表达式完成,还是我最好只编写一个pyparsing解析器?

1 个答案:

答案 0 :(得分:3)

你可以尝试这种模式:

(?s)\\.(*SKIP)(*F)|(?s)(\${(?>[^$}\\]+|\\.|(?1))*})

online example

细节:

(?s)
\\.               # an escaped character
(*SKIP)           # skip the matched content if the pattern fails later
(*F)              # force the pattern to fail
|
(?s)
(
    \${
    (?>           # open a atomic group
        [^$}\\]+  # all that is not a backslash, a $ or a }
      |           # OR
        \\.       # an escaped character
      |           # OR
        (?1)      # recurse to group 1
    )*            # repeat the atomic group zero or more times
    }               
)

主要思想是避免将转义后的美元跟随开头大括号视为开头标记。

注意:而不是为每个分支使用内联修饰符(?s),您可以删除它们并使用全局修饰符s

要完全严谨,您可以通过在原子组中添加替代$来允许\$(?!{)内容中没有后面的开头花括号。 (在递归之前)

关于(*SKIP)(*FAIL)

(*SKIP)(*FAIL)被称为回溯控制动词

当模式失败时,NFA正则表达式引擎的默认行为是使用回溯机制。这些动词允许控制这种机制。

更具体地说,组合subpattern(*SKIP)(*FAIL)的目标是从匹配结果中排除子模式匹配的内容,并禁止正则表达式引擎尝试使用匹配的子字符串进行任何其他操作。跳过子字符串。

您可以看到full explanation here.

关于原子分组:

atomic group是非捕获组。唯一的区别是一旦达到右括号,就不再允许正则表达式引擎在括号内匹配的字符内回溯。它只能到集团面前的位置。原子组使匹配的子串不可分割(原子)。

此处,如果模式稍后失败,则原子组会阻止catastrophic backtracking与此类构造(?:A+|B)+发生的情况。