使用正则表达式,如何有效地匹配双引号与嵌入双引号之间的字符串?

时间:2013-06-11 11:54:10

标签: regex

让我们有一个文本,我们希望在双引号之间匹配所有字符串;但在这些双引号中,可以引用双引号。例如:

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

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

1 个答案:

答案 0 :(得分:14)

匹配此类输入的一种非常有效的解决方案是使用normal* (special normal*)*模式;这个名字引自Jeffrey Friedl的优秀着作,Mastering Regular Expressions

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

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

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

带嵌入双引号的字符串

让我们再看一遍上面的例子;请注意,此文本示例可能位于您输入的任何位置:

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

无论你怎么努力,没有多少“点加上贪婪/懒惰量词”魔法会帮助你解决它。相反,将引号之间的输入分类为正常和特殊:

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

将其替换为normal* (special normal*)*模式,这给出了以下正则表达式:

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

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

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

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

带破折号分隔符的单词

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

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

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

示例输入:

the-word-to-match

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

  • normal:小写的ASCII字母:[a-z];
  • special:短划线:-

该模式的规范形式将是:

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

但正如我们所说:

  • 我们不希望以破折号开头的字词:第一个*应该变为+;
  • 找到破折号后,其后应至少有一个字母:第二个*应变为+

我们最终得到:

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

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

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

其他运营商变体

上述示例仅限于将*替换为+,但当然您可以根据需要添加任意数量的变体。一个超经典的例子是IP地址:

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

给出了expresison(用词锚装饰):

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

结论

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

提示

  • 您很可能不需要(或想要)捕获重复的部分((special normal*)部分);因此,建议您使用非捕获组。例如,对引用的字符串使用"[^\\"]*(?:\\"[^\\"]*)*"。事实上,如果你想要它,在这种情况下捕获几乎永远不会产生预期的结果,因为重复捕获组只会给你最后捕获(所有先前的重复将被覆盖),除非你在.NET中使用这种模式。 (感谢@ohaal)