对可选捕获子表达式的模式反向引用

Ant*_*nov 1 regex bash backreference

尝试使用Bash内置的正则表达式匹配来解析以下类型的字符串,这些字符串将被转换为Perl替换表达式(引号不是数据的一部分)

'~#A#B#'
#^ ^ ^-- Replacement string.
#| +---- Pattern string.
#+------ Regular expression indicator (no need to escape strings A and B),
#        which is only allowed if strings A and B are surrounded with ##.
#        Strings A and B may not contain #, but are allowed to have ~.

'#A#B#'
#^------ When regex indicator is missing, strings A and B will be escaped.

'A#B'
#        Simplified form of '#A#B#', i. e. without the enclosing ##.
#        Still none of the strings A and B is allowed to contain # at any position,
#        but can have ~, so leading ~ should be treated as part of string A.
Run Code Online (Sandbox Code Playgroud)

我尝试了以下模式(同样,没有引号):

'^((~)?(#))?([^#]+)#([^#]+)\3$'
Run Code Online (Sandbox Code Playgroud)

也就是说,它声明了前导~#可选(并且~在其中更可选),然后捕获部分AB,并且#仅当尾随出现在前导中时才要求尾随出现。前导#仅用于反向引用匹配 - 其他地方不需要它,而~被捕获后由脚本检查。

但是,该模式仅适用于最完整类型的输入数据:

'~#A#B#'
'#A#B#'
Run Code Online (Sandbox Code Playgroud)

但不是为了

'A#B'
Run Code Online (Sandbox Code Playgroud)

即,只要缺少前导部分,就\3无法匹配。但是如果\3替换为.*,则匹配成功,可以看出这${BASH_REMATCH[3]}是一个空字符串。这是我不明白的事情,前提是未设置的变量在 Bash 中被视为空字符串。那么如何将反向引用与可选内容相匹配?

作为一种解决方法,我可以编写一个替代模式

'^(~?)#([^#]+)#([^#]+)#$|^([^#]+)#([^#]+)$'
Run Code Online (Sandbox Code Playgroud)

但它会为每种可能的情况产生不同的捕获组,这使得代码不那么直观。

重要的提示。正如@anubhava 在他的评论中提到的,在某些 Bash 构建中可能无法使用反向引用匹配(也许这是构建选项的问题,而不是版本号的问题,甚至是某些外部库的问题)。这个问题当然是针对那些支持这种功能的 Bash 环境的。

Ara*_*Fey 6

有两种方法可以处理这个问题:

  1. 不要将组设为可选(换句话说,允许它根本不匹配),而是将其设为强制性但匹配空字符串。换句话说,更改结构如(#)?to (#?)

  2. \3仅当第 3 组匹配时才使用条件匹配反向引用。为此,请更改\3(?(3)#|)

通常,第一个选项更可取,因为它具有更好的可读性。此外,bash 的正则表达式似乎不支持条件结构,因此我们需要使选项 1 起作用。这很困难,因为附加条件~仅在#同时存在时才允许。如果 bash 支持前瞻,我们可以做类似((~)(?:#))?(#?). 但既然没有,我们需要发挥创造力。我想出了以下模式:

^((~(#))|(#?))([^#]+)#([^#]+)(\3|\4)$
Run Code Online (Sandbox Code Playgroud)

演示

这个想法是利用交替运算符|来处理两种不同的情况:文本要么以 开头~#,要么不以 开头。如果可能,在第 2 组和第 3组中((~(#))|(#?))捕获,但如果没有,则它只在第 4 组中捕获(如果存在)。然后我们可以在最后使用来匹配结尾,如果有一个开头(请记住,第 3 组)捕获如果开始用文字和组4捕获或空字符串,如果文本没有下手)。~##~#(\3|\4)##~##~#