我们为什么要在正则表达式中更喜欢否定字符类?*?

时间:2012-04-08 07:49:18

标签: regex regex-negation

我正在看Regex的教程。

它是关于如何从这段html中获取class属性

<pre class="ruby" name="code">

使用的正则表达式是

<pre class="([^"]+)" name="code">

他们建议使用上面的那个而不是

<pre class="(.+)" name="code">

“因为它超出了报价。”

我不明白他们的意思。无论如何它只是工作但是为什么推荐第一个正则表达式。我错过了什么吗?请赐教。

提前致谢。

4 个答案:

答案 0 :(得分:4)

.+贪婪地匹配。例如,在

<pre class="ruby" size="medium" name="code"> 

它会匹配ruby" size="medium。更糟糕的是,如果在同一行上有两个标记,它将在标记边界上匹配:

<pre class="ruby" name="code">foo</pre> <pre class="python" name="code">bar</pre>

会产生ruby" name="code">foo</pre> <pre class="python

因此,只要您确切了解HTML的外观,.+就可以正常工作,但只要它意外地发生变化(因为HTML不会这样做),您的正则表达式就不会失败(如第二个会)但它会匹配错误的东西。

因此,第二个正则表达式更安全(因为它更准确地说明允许匹配的内容)。您通常应该尽量避免使用简单的.+.*“匹配任何内容”,而应考虑您想要匹配的内容。

也就是说,出于完全相同的原因,你不应该尝试将HTML和其他标记语言与正则表达式匹配,因为有更好的工具。

答案 1 :(得分:2)

正则表达式匹配通常会尝试匹配最长的正则表达式。因此&#34;([^&#34;] +)&#34;只匹配它遇到的第一个引用。另一方面,&#34;(。+)&#34;将从第一个引用匹配到字符串中的 last 引用。

例如,如果我们将它们应用于您的问题,则第一个匹配"ruby",因为它是您问题中的第一个引用字符串。第二个匹配从"rubybeyond the quote"一直匹配,因为它是问题中的最后一个引号(并且还包括其他几个引用的字符串。

答案 2 :(得分:2)

考虑这个例子:

<pre class="scooby" name="not-code">
  content
</pre>

...other HTML...

<pre class="ruby" name="code">
  content
</pre>

使用此正则表达式[*]:

<pre class="(.+)" name="code">

...第一部分 - <pre class=" - 开始匹配第一个标记,然后(.+)消耗文档的其余部分。但正则表达式的其余部分 - " name="code"> - 在那里无法匹配,所以它会在第二个标记中向后退,直到找到它可以的位置。结果:该小组最终捕获从scoobyruby的所有内容。

即使您使用非贪婪的(.+?)而非贪婪的(.+),也是如此。人们常说非贪婪量词导致正则表达式返回最短的匹配,但事实并非如此。就像贪婪的正则表达式一样,在第一次机会时开始匹配;它只是停止匹配。像这样的情况,非贪婪量词没有好处,并不罕见。

另一件需要考虑的事情是,当不可能匹配时 - 例如,如果<pre>个标签带有第一个属性class="~whatever~",但没有带有name="code"属性的标签。在每一个,贪婪的(.+)将吞噬整个文件,然后退回,直到它到达起点然后放弃。非贪婪的(.+?)不会回溯,但它会扫描整个页面,它会更慢地执行它(它有效地在每个位置对" name="code">进行预测)。

这个正则表达式:

<pre class="([^"]+)" name="code">

...它永远不必扫描超出标签的末尾以确定它是否匹配。

始终考虑如果不可能匹配会发生什么。这可能是最常见的监督正则表达式作者,也是导致性能问题最多的那个。

[*]我假设比赛正在DOTALL模式(a.k.a。单线模式)进行,仅供参考。

答案 3 :(得分:1)

取消课程通常会更加具体地说明您想要匹配的内容,并有助于防范Catastrophic Backtracking等情况。

杰夫阿特伍德不久前写了一篇有趣的blog post,他举了一个看似无辜的正则表达式的例子:(x+x+)+y可以(几乎)永远地完成处理。即使主题很小,也是如此:xxxxxxxxxxxxxxxxxxxx

给它一个阅读,它真的很有趣。