【问题标题】:Pattern backreference to an optional capturing subexpression对可选捕获子表达式的模式反向引用
【发布时间】:2016-05-18 11:11:46
【问题描述】:

尝试使用 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.

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

'^((~)?(#))?([^#]+)#([^#]+)\3$'

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

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

'~#A#B#'
'#A#B#'

但不是为了

'A#B'

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

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

'^(~?)#([^#]+)#([^#]+)#$|^([^#]+)#([^#]+)$'

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

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

【问题讨论】:

  • 试试'^(~?#?)([^#]+)#([^#]+)\1$',或者如果~不需要在字符串的开头和结尾都检查,试试^~?(#?)([^#]+)#([^#]+)\1$
  • 很抱歉,不太清楚,但只有在存在# 时才能出现前导~——它们不是两个独立的部分。
  • @WiktorStribiżew 不,你仍然没有得到它:如果~ 已经存在,前面的# 不能丢失,所以你的表达不适合我的任务,即使它适用于某些人输入。也就是说,它将 ~A#B 拆分为 ('~' 'A' 'B') 而不是 ('~A' 'B') — 当没有哈希包含字符串时,前导 ~ 没有特殊含义,必须被视为第一个字符串的一部分。
  • 对不起,这个问题真的不清楚。请注意,一旦捕获的捕获组内容将被视为单个原子,并且反向引用将指向该文本。其目的是准确匹配匹配的内容。现在,您不能在 Bash 正则表达式中使用环视,只能在 Perl 中使用,因此无法在开头限制 ~,也无法使用分支重置。我在这里看不到任何解决方法。

标签: regex bash backreference


【解决方案1】:

有两种方法可以解决这个问题:

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

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

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

^((~(#))|(#?))([^#]+)#([^#]+)(\3|\4)$

Demo.

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

【讨论】:

  • 选项 1 (^((~)?(#)|)([^#]+)#([^#]+)\3$) 的优点,但不幸的是,它具有相同的效果 — 当采用替代路线时,\3 不再匹配,尽管 ${BASH_REMATCH[*]} 看起来与预期一样.然而,条件匹配似乎不起作用——Bash 是否支持它?
  • Антон, \3 仅指(#)。您需要使用\1
  • @AntonSamsonov 应该是(#?),而不是(#)|
  • 我有 BASH 4.3.42(1)-release,但它仍然不可用。 s='A-A'; re='^(A)-\1$'; [[ $s =~ $re ]] 总是失败。
  • @anubhava 确实,虽然我的主要 4.2.47(1)-release (openSUSE 42.1) 和我同事的 4.3.11(1)-release (Ubuntu 14.04) 运行4.3.42(4)-release (Cygwin 2.4) 中完全相同的脚本会导致不匹配。也许它受到一些编译选项的影响。我会记住这一点,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-06
  • 1970-01-01
  • 2022-11-21
  • 1970-01-01
相关资源
最近更新 更多