我正在尝试使用递归正则表达式来匹配类似bash的变量扩展。基本上,我应该能够匹配如下字符串:
${FOO=BAR}
${FOO=${BAR=BAZ}}
但也处理这样的输入:
${FOO=\${BAR=BAZ}}
${FOO={${BAR=BAZ}}
在第一种情况下,它应匹配,但不包括最终}
,第二种情况应完全匹配。这是因为我试图将双字符开头${
与单字符结束}
匹配。打开和关闭都应该能够逃脱。
我已经达到了PCRE正则表达式\$\{(?:[^{}]|(?R))*\}
。但这并不能正确处理转义。如果我将其修改为(?:^|[^\\])(?:\\\\)*(\$\{(?:[^{}]|(?R))*\})
,则只有最外面的转义符才能正确匹配。
这可以用正则表达式完成,还是我最好只编写一个pyparsing解析器?
答案 0 :(得分:3)
你可以尝试这种模式:
(?s)\\.(*SKIP)(*F)|(?s)(\${(?>[^$}\\]+|\\.|(?1))*})
细节:
(?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)
的目标是从匹配结果中排除子模式匹配的内容,并禁止正则表达式引擎尝试使用匹配的子字符串进行任何其他操作。跳过子字符串。
关于原子分组:
atomic group是非捕获组。唯一的区别是一旦达到右括号,就不再允许正则表达式引擎在括号内匹配的字符内回溯。它只能到集团面前的位置。原子组使匹配的子串不可分割(原子)。
此处,如果模式稍后失败,则原子组会阻止catastrophic backtracking与此类构造(?:A+|B)+
发生的情况。