【问题标题】:Exponential Regex Problem指数正则表达式问题
【发布时间】:2011-09-16 19:53:41
【问题描述】:

有人可以帮我把这个正则表达式改写成非指数的吗?

我正在使用 perl 来解析电子邮件数据。我想从数据中提取电子邮件地址。这是我一直在使用的正则表达式的缩短版本:

my $email_address = qr/(?:[^\s@<>,":;\[\]\(\)\\]+?|"[^\"]+?")@/i

为简单起见,我删除了正则表达式后面的域部分。 (它不会引起任何问题。)

这将找到一个符合 RFC 的电子邮件地址,其中包含非电子邮件元字符或后跟 @ 的“引用”字符串。使用 OR '|'具有两种不同多字符模式的正则表达式的一部分会产生指数问题。

问题是,当我在几千个字符长的数据行上释放它时。

$ wc line7.txt 
1    221 497819 line7.txt

(很抱歉,我目前无法提供输入数据,我可能稍后可以模拟一些。)

就像将 (a*b*)* 重写为 (a|b)* 一样,我需要重写这个正则表达式。

将其拆分为两个单独的正则表达式会在代码更改中创建更多工作,然后我愿意在这一点上执行。虽然它会解决我的问题。

最终的目标机器位于 Hadoop 集群上。所以我想避免使用 Hadoop 版本的 perl 不附带的 CPAN 模块。 (我得检查一下是否可以使用Email::Find。)这是我在工作中遇到的问题。

【问题讨论】:

  • 去掉不必要的 \ 字符,让你的 RE 更具可读性:qr/(?:[^\s@,":;\[\]()\\]+? |"[^"]+?")@/i

标签: regex perl parsing email


【解决方案1】:

您是否考虑过 CPAN 模块 Email::ValidEmail::Find

除非这是为了您自己的乐趣或教育,否则您几乎肯定不应该尝试编写自己的电子邮件地址匹配正则表达式。如果你想知道这样的东西实际上是什么样子,请参阅 Jeffrey Friedl 的 Mastering Regular Expressions。 (提示:它有 6,598 字节长。)

【讨论】:

  • 如果我不必经历将 Email::Find 添加到我所在的 Hadoop 集群的痛苦,这将起作用。不过还是谢谢你的建议。
【解决方案2】:
qr/(?:(?>[^\s@<>,":;\[\]\(\)\\])+|"[^\"]{0,62}")@/i

(?&gt;expression) 部分可防止回溯。它应该是安全的,因为非引用部分和引用部分之间不能有重叠。

我删除了惰性重复 +?,因为交替的部分已经分别寻找 @"。短语可能是回溯的重要来源,所以我查看了 Wikipedia 文章,该文章指出本地部分(@ 之前)只能有 64 个字符长(减去两个引号产生{0,62}(如果""@ 无效,然后将其更改为 {1,62}.... 我不打算让它成为一个功能齐全的电子邮件解析器。那是你的工作。我只是为灾难性的回溯提供帮助。)祝你好运!

【讨论】:

  • 这几乎解决了它。我把它变得更严格了。 qr/(?:[^\s@&lt;&gt;,":;\[\]\(\)\\]{1,64}|"[^"]{0,64}")@/i 非常感谢!
  • 我想你的意思是(?&gt;[^\s@&lt;&gt;,":;\[\]\(\)\\]+) - 与+ inside 原子组。
【解决方案3】:

据我了解,如果您不小心,非贪婪匹配会很昂贵。它可能会做很多很多的回溯。 http://blog.stevenlevithan.com/archives/greedy-lazy-performance

我经常使用的一个技巧是,一旦我发现数据无法保存任何数据,就会破坏性地提取数据。另一个技巧是做一个非回溯匹配(\@{1}+ 或类似的),如果有一些东西可能会向你发出信号,你绝对需要在那里解析一个电子邮件地址。

在您的具体示例中,也许您可​​以限制电子邮件地址中的字符数?使用 {1,80}

而不是 @ 左侧的 +

【讨论】:

  • 我喜欢这个解决方案。我将不得不在工作中尝试它是否有效。根据RFC 5321 - 4.5.3.1.1,本地部分不能超过64个八位字节。
【解决方案4】:

只需将+? 更改为+ 即可; ? 表示希望尽可能少地匹配,这根本不是您想要的。

要么我看错了什么,要么你的问题出在你没有向我们展示的正则表达式部分。或者您所展示的内容与您实际尝试的内容之间存在一些差异。在任何情况下,您都可以尝试将+? 更改为++ 或将整个(?:...)@ 包含在(?&gt; ... ) 中。

在您的实际正则表达式中@ 之前是否有+?如果是这样,只需将(?: 更改为(?&gt; 并将那个 + 设置为++ 将是一个非常好的主意。

【讨论】:

  • 未引用电子邮件的左侧部分可以使用+,但右侧需要+?,以免超出额外的"。该行中可能有多个引用的电子邮件。我已经尝试了两种方法,但它仍然呈指数级增长。
  • 它不能超过一个额外的",它只匹配非"
  • 错误,其他 cmets 之一提到了 @ 左侧的 +;我在那里没有看到,但如果有的话,你的问题会更有意义。
  • 对不起。你确实是对的。它不能消耗额外的"。出于某种原因,我想起了"(.*?)"。所以我说的是构成变更的组件。第一个是右侧的[^\s\@\&lt;\&gt;\,\"\:\;\[\]\(\)\\]+?,第二个是左侧的"[^\"]+?。明天我会尝试让两者都变得贪婪,看看会发生什么,尽管我怀疑我会因为this 而遇到同样的问题。似乎正则表达式引擎无论如何都会回溯。
  • 我也不认为 perl 5.8 支持 ++ 选项。但我仍然可以按照你的建议使用原子组。
【解决方案5】:

如果很多行包含电子邮件地址,那么在应用 RE 之前进行快速预测试如何:

if (我的 $ix = index($line, '@') > 0)
{ #test 邮箱地址在这里
    . . .
    #还有另一个疯狂的想法,您可以尝试减少实际解析的字符串长度:
    我的 $maxLength = 100; #最大支持的电子邮件地址长度(最多 @)
    if ( substr( $line, MAX( $ix - $maxLength, 0), $maxLength ) =~ /YourRE/ )
}

(是的,>任何以@开头的行都不能是电子邮件地址)

【讨论】:

    猜你喜欢
    • 2011-01-25
    • 1970-01-01
    相关资源
    最近更新 更多