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

fge*_*fge 6 regex

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

"He said \"Hello\" to me for the first time"
Run Code Online (Sandbox Code Playgroud)

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

fge*_*fge 14

匹配此类输入的一种非常有效的解决方案是使用该normal* (special normal*)*模式; 这个名字引自Jeffrey Friedl的精彩书籍,掌握正则表达式.

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

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

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

嵌入双引号的字符串

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

"He said \"Hello\" to me for the first time"
Run Code Online (Sandbox Code Playgroud)

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

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

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

[^\\"]*(\\"[^\\"]*)*
Run Code Online (Sandbox Code Playgroud)

添加双引号以匹配全文可以得到最终的正则表达式:

"[^\\"]*(\\"[^\\"]*)*"
Run Code Online (Sandbox Code Playgroud)

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

带破折号分隔符的单词

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

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

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

样本输入:

the-word-to-match
Run Code Online (Sandbox Code Playgroud)

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

  • normal:小写,ASCII字母:[a-z];
  • 特殊:破折号: -

该模式的规范形式将是:

[a-z]*(-[a-z]*)*
Run Code Online (Sandbox Code Playgroud)

但正如我们所说:

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

我们最终得到:

[a-z]+(-[a-z]+)*
Run Code Online (Sandbox Code Playgroud)

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

\b[a-z]+(-[a-z]+)*\b
Run Code Online (Sandbox Code Playgroud)

其他运营商变化

上面的例子把自己限制替换*+,当然,如你所愿,你可以有很多变化.一个超经典的例子是IP地址:

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

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

\b\d{1,3}(\.\d{1,3}){3}\b
Run Code Online (Sandbox Code Playgroud)

结论

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

提示

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