我正在尝试使用递归正则表达式来匹配类似bash的变量展开。基本上,我应该能够匹配如下字符串:
${FOO=BAR}
${FOO=${BAR=BAZ}}
但也可以处理这样的输入:
${FOO=${BAR=BAZ}}
${FOO={${BAR=BAZ}}
在第一种情况下,它应该匹配到,但不包括最终的}
,第二种情况应该完全匹配。这是因为我试图将两个字符的开头${
和一个字符的结尾}
相匹配。打开和关闭都应该能够逃脱。
我已经达到了PCRE正则表达式${(?:[^{}]|(?R))*}
。但这并不能正确处理转义。如果我把它修改为(?:^|[^\])(?:\\)*(${(?:[^{}]|(?R))*})
,那么只有最外面的转义符匹配正确。
这可以用regex完成吗?还是我最好只写一个pyparsing解析器?
您可以尝试以下模式:
(?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)
的目标是从匹配结果中排除子模式匹配的内容,并禁止正则表达式引擎对匹配的子字符串进行任何其他尝试。跳过子字符串。
你可以在这里看到完整的解释。
关于原子分组:
原子组是一个非捕获组。唯一的区别是,一旦到达右括号,正则表达式引擎就不允许在括号之间匹配的字符内回溯。它只能到达组之前的位置。原子组使匹配的子字符串不可分割(原子)。
在这里,如果模式稍后失败,原子组可以防止这种构造(?:A+|B)+
可能发生的灾难性回溯。