替换字符串中的反向引用语法(为啥是美元符号?)

Posted

技术标签:

【中文标题】替换字符串中的反向引用语法(为啥是美元符号?)【英文标题】:Backreferences Syntax in Replacement Strings (Why Dollar Sign?)替换字符串中的反向引用语法(为什么是美元符号?) 【发布时间】:2011-02-22 20:36:18 【问题描述】:

在 Java 中,似乎在其他一些语言中,模式中的反向引用前面有一个反斜杠(例如 \1\2\3 等),但在替换字符串中它们前面有一个美元符号(例如$1$2$3$0)。

这里有一个 sn-p 来说明:

System.out.println(
    "left-right".replaceAll("(.*)-(.*)", "\\2-\\1") // WRONG!!!
); // prints "2-1"

System.out.println(
    "left-right".replaceAll("(.*)-(.*)", "$2-$1")   // CORRECT!
); // prints "right-left"

System.out.println(
    "You want million dollar?!?".replaceAll("(\\w*) dollar", "US\\$ $1")
); // prints "You want US$ million?!?"

System.out.println(
    "You want million dollar?!?".replaceAll("(\\w*) dollar", "US$ \\1")
); // throws IllegalArgumentException: Illegal group reference

问题:

在替换字符串中使用$ 进行反向引用是Java 独有的吗?如果不是,是什么语言开始的?哪些口味使用它,哪些不使用? 为什么这是个好主意?为什么不坚持相同的模式语法?这不会导致语言更有凝聚力和更容易学习吗? 如果上面的语句 1 和 4 是“正确”语句而不是 2 和 3,那么语法会不会更精简?

【问题讨论】:

\1\2 是八进制转义序列,分别用八进制数字 1 和 2 描述字符(参见 java.sun.com/docs/books/jls/second_edition/html/…)。这就是为什么您需要不同的引用语法。 另见***.com/questions/3556558/… 【参考方案1】:

在替换字符串中使用 $ 进行反向引用是 Java 独有的吗?

没有。 Perl 使用它,而且 Perl 肯定早于 Java 的 Pattern 类。 Java 的正则表达式支持是根据 Perl 正则表达式明确描述的。

例如:http://perldoc.perl.org/perlrequick.html#Search-and-replace

为什么这是个好主意?

显然你认为这不是一个好主意!但这是一个好主意的一个原因是使 Java 搜索/替换支持(更多)与 Perl 兼容。

$ 可能被视为比\ 更好的选择还有另一个可能原因。也就是说,\ 必须在 Java 字符串文字中写为 \\

但这一切纯属猜测。做出设计决定时,我们都没有在房间里。最终,他们为什么以这种方式设计替换 String 语法并不重要。决策已经做出并具体化,任何进一步的讨论都纯属学术性的......除非您恰好正在为 Java 设计一种新语言或新的正则表达式库。

【讨论】:

+1 同意...现在很多正则表达式引擎都按照他们的方式做事,因为 Perl 就是这样做的。所以要真正理解它,你必须理解 Perl 背后的原因。 (警告:不要在家里尝试) Perl pwns 在正则表达式。你现在到处都能看到它:javascript、XML、Java、php 等等。 "Perl pwns at regex" - 有人愿意帮我把它翻译成英文吗?【参考方案2】:

在做了一些研究之后,我现在明白了这些问题:Perl 不得不为模式反向引用和替换反向引用使用不同的符号,而 java.util.regex.* 没有有 效仿,它选择效仿,不是出于技术原因,而是出于传统原因。


在 Perl 方面

(请记住,我目前对 Perl 的所有了解都来自阅读 Wikipedia 文章,因此请随时纠正我可能犯的任何错误)

不得不在 Perl 中这样做的原因如下:

Perl 使用 $ 作为标记(即附加到变量名的符号)。 Perl 字符串文字是变量插值。 Perl 正则表达式实际上将组捕获为变量$1$2 等。

因此,由于 Perl 的解释方式及其正则表达式引擎的工作方式,必须在模式中使用前面的斜杠来表示反向引用(例如 \1),因为如果使用符号 $ 代替(例如 @ 987654329@),它会导致意外的变量插值到模式中。

替换字符串,由于它在 Perl 中的工作方式,在每个匹配的上下文中进行评估。 Perl 在这里使用变量插值是最自然的,因此正则表达式引擎将组捕获到变量 $1$2 等中,以使其与语言的其余部分无缝工作。

参考文献

Wikipedia/String literal - variable interpolation Wikipedia/Sigil (computer programming)

在 Java 方面

Java 是一种与 Perl 非常不同的语言,但最重要的是这里没有变量插值。此外,replaceAll 是一个方法调用,与 Java 中的所有方法调用一样,参数在调用方法之前被评估一次。

因此,仅靠变量插值功能是不够的,因为本质上必须在每次匹配时重新评估替换字符串,而这不是 Java 中方法调用的语义。在replaceAll 被调用之前评估的变量插值替换字符串实际上是没有用的;插值需要发生在方法中,在每场比赛中。

由于这不是 Java 语言的语义,replaceAll 必须手动执行此“即时”插值。因此,绝对没有技术原因为什么$ 是替换字符串中反向引用的转义符号。很可能是\。相反,模式中的反向引用也可以使用$ 而不是\ 进行转义,并且在技术上仍然可以正常工作。

Java 使用正则表达式的原因纯粹是传统的:它只是遵循 Perl 设置的先例。

【讨论】:

在正则表达式中$ 已经被用作锚;使用它作为反向引用的标志会非常混乱,如果不是不可能的话。在替换字符串中,反斜杠用于消歧;如果$10 可以指第十组,但您希望它表示第一组后跟零,则改写$1\0。当然,您可以使用它来转义文字 $。这与它在正则表达式和 Java 字符串文字中的使用是一致的。所以这不是一个完全武断的选择。

以上是关于替换字符串中的反向引用语法(为啥是美元符号?)的主要内容,如果未能解决你的问题,请参考以下文章

将整个单词与字符串中的前导或尾随特殊符号(如美元)匹配

vim替换命令

vim替换命令

PHP正则表达式 - 替换一个反向引用[重复]

JavaScript 字符串替换中的子匹配组引用是不是有分隔符/消歧语法?

linux 特殊符号怎样用sed替换