6

让我们有一个文本,我们想要匹配双引号之间的所有字符串;但在这些双引号内,可以有双引号。例子:

"He said \"Hello\" to me for the first time"

使用正则表达式,你如何有效地匹配它?

4

1 回答 1

17

匹配此类输入的一个非常有效的解决方案是使用normal* (special normal*)*模式;这个名字是从杰弗里弗里德尔的优秀著作《掌握正则表达式》中引用的。

这是一种通常用于匹配由常规条目(正常部分)和中间分隔符(特殊部分)组成的输入的模式。

请注意,就像所有的正则表达式一样,它应该在没有更好的选择时使用;虽然可以使用这种模式来解析 CSV 数据,例如,如果您使用 Java,则最好使用 OpenCSV。

另请注意,虽然模式名称中的量词是星号(即零个或多个),但您可以根据需要更改它们。

嵌入双引号的字符串

让我们再举一次上面的例子;并且请考虑此文本示例可能在您输入的任何位置:

"He said \"Hello\" to me for the first time"

无论您多么努力,再多的“点加贪婪/懒惰量词”魔法都无法帮助您解决它。相反,将引号之间的输入分类为普通和特殊:

  • normal 不是反斜杠或双引号:[^\\"];
  • special 是反斜杠后跟双引号的序列:\\".

将其代入normal* (special normal*)*模式中,得到以下正则表达式:

[^\\"]*(\\"[^\\"]*)*

添加双引号以匹配全文给出最终的正则表达式:

"[^\\"]*(\\"[^\\"]*)*"

您会注意到,这也将匹配空的引用字符串。

带有破折号分隔符的单词

在这里,我们将不得不在量词上使用一个变体,因为:

  • 我们不想要空话,
  • 我们不想要以破折号开头的单词,
  • 当一个破折号出现时,它必须在另一个破折号之前至少有一个字母,如果有的话。

为简单起见,我们还假设只允许使用小写的 ASCII 字母。

样本输入:

the-word-to-match

让我们再次分解为正常和特殊:

  • 正常:小写,ASCII 字母:[a-z];
  • 特别:破折号:-

该模式的规范形式是:

[a-z]*(-[a-z]*)*

但正如我们所说:

  • 我们不希望以破折号开头的单词:第一个*应该变成+;
  • 当找到一个破折号时,它后面应该至少有一个字母:第二个*应该变成+.

我们最终得到:

[a-z]+(-[a-z]+)*

在其周围添加单词锚点以获得最终结果:

\b[a-z]+(-[a-z]+)*\b

其他运算符变体

上面的示例仅限于替换*+,但当然您可以根据需要进行多种变体。一个超经典的例子是 IP 地址:

  • 正常最多三位数字 ( \d{1,3}),
  • 特别是点:(\.),
  • 第一个normal只出现一次,因此没有量词,
  • 内部也只normal出现(special normal*)一次,因此没有量词,
  • 最后该(special normal*)部分恰好出现了 3 次,因此{3}.

这给出了表达式(用词锚装饰):

\b\d{1,3}(\.\d{1,3}){3}\b

结论

这种模式的灵活性使其成为您的正则表达式工具箱中最有用的工具之一。虽然存在许多问题,如果存在库,则不应使用正则表达式,但在某些情况下,您必须使用正则表达式。一旦你练习了一下,这将成为你最好的朋友之一!

提示

  • 您很可能不需要(或不想)捕获重复的部分((special normal*)零件);因此建议您使用非捕获组。例如,"[^\\"]*(?:\\"[^\\"]*)*"用于带引号的字符串。事实上,如果你想要它,在这种情况下捕获几乎永远不会产生预期的结果,因为重复捕获组只会给你最后一次捕获(所有以前的重复都将被覆盖),除非你在。网。(感谢@ohaal)
于 2013-06-11T12:01:34.997 回答