正则表达式识别电子邮件地址难吗?

62

我最近在某个地方读到,编写匹配电子邮件地址的正则表达式,考虑标准的所有变化和可能性是极其困难的,明显比人们最初想象的要复杂得多。

为什么会这样呢?

是否有已知且经过验证的正则表达式可以完全实现这一点?

有哪些很好的替代方法可以用于匹配电子邮件地址而不使用正则表达式?


我认为你所读的内容不是关于“根据标准验证电子邮件地址”,而是“验证实际电子邮件地址”。即使措辞相同,两者之间的区别并不微小。目前,下面的答案混淆了这两个概念。也许您可以澄清一下问题? - bzlm
我之前写了一篇关于这个的博客文章 -- 在这里:如何使用正则表达式验证电子邮件地址,它指出了捕获所有不同边缘情况的一些挑战。 - Kevin Bedell
1
用一条正则表达式来解析复杂文本是常见的蠢事。但是用一组正则表达式来解析复杂文本(比如 C 语言源代码)却很容易,例如使用 lex 和 yacc。这种方法还支持递归。把责任归咎于 Larry。 :) - Sam Watkins
显示剩余2条评论
19个回答

65

对于正式电子邮件规范,确实由于注释等内容的递归(特别是如果您不先将注释转换为空格),以及各种不同的格式(电子邮件地址并不总是someone@somewhere.tld),通过正则表达式技术上是不可能的。你可以接近实现(使用一些庞大且难以理解的正则表达式模式),但检查电子邮件的更好方法是执行非常熟悉的握手:

  • 他们告诉您他们的电子邮件地址
  • 您向他们发送带有Guid的确认链接
  • 当他们点击链接时,您就知道:

    1. 电子邮件地址正确
    2. 它存在
    3. 他们拥有它

远比盲目接受电子邮件地址要好得多。


8
如果你在写网站,好的建议可能很有用,但如果你在写邮件服务器/客户端,则可能效果不佳 :-) - Johan
5
如果你正在编写电子邮件客户端或服务器,那么你不应该虚假解析你需要解析的唯一内容(几乎是这样)。 - Marcin
1
你如何在不盲目接受他们的电子邮件地址的情况下向他们发送确认邮件? - janm
2
@janm:电子邮件服务器会为您进行验证:如果消息已传递(并且其中的链接被点击),则地址是有效的。 - David Schmitt
1
如果您有一个可靠的电子邮件服务器,并且可以可靠地获取其电子邮件地址,那就太好了(例如,使用Unix样式exec(2)的qmail、postfix)。如果没有,仍然必须小心处理,就像来自不受信任的来源的任何数据一样。 - janm
@Johan:将“点击链接”替换为“回复邮件”。 - Jason S

22
有许多Perl模块(例如)可以做到这一点。不要尝试编写自己的正则表达式来完成它。看看Mail::VRFY,它将进行语法和网络检查(某个SMTP服务器是否接受这个地址)。 https://metacpan.org/pod/Mail::VRFY RFC::RFC822::Address - 一个递归下降的电子邮件地址解析器。 https://metacpan.org/pod/RFC::RFC822::Address Mail::RFC822::Address - 基于正则表达式的地址验证,仅就其疯狂的正则表达式而言值得一看。 http://ex-parrot.com/~pdw/Mail-RFC822-Address.html 其他语言也有类似的工具。以下是疯狂的正则表达式...
(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
)+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:
\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(
?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ 
\t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\0
31]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\
](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+
(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:
(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)
?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\
r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[
 \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)
?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t]
)*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[
 \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*
)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
)+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)
*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+
|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r
\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t
]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031
]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](
?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?
:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?
:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?
:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?
[ \t]))*"(?:(?:\r\n)?[ \t])*)*:(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] 
\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|
\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>
@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"
(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t]
)*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?
:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[
\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-
\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(
?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;
:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([
^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\"
.\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\
]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\
[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\
r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] 
\000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]
|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \0
00-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\
.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,
;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?
:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*
(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[
^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]
]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)(?:,\s*(
?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(
?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[
\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t
])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t
])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?
:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|
\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:
[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\
]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)
?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["
()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)
?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>
@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[
 \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,
;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t]
)*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?
(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:
\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\[
"()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])
*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])
+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\
.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(
?:\r\n)?[ \t])*))*)?;\s*)

1
说实话,这并不让我感到惊讶——所有我见过的尝试通过regexp验证电子邮件符合实际标准的尝试在某种程度上都是荒谬的——我甚至不会尝试去理解它。如果一个正则表达式的大小只有十分之一,那么可能意味着您不应该使用它 ;) - mmaibaum
31
我会把这归类为“密码学”。 - steffenj
3
个人而言,我宁愿使用一个简化的正则表达式,也不想使用那个复杂的工具(即使它可以处理99.95%的真实情况)... 或者干脆不用正则表达式,而是做“握手”来解决问题。 - Marc Gravell
1
@Simon,这是正确的。在您应用此正则表达式之前,您需要对字符串进行预处理以删除注释,并且RFC822已经非常过时了;它来自1982年(!) - porges
3
我认为你把APL和Perl搞混了;在APL中,“Hello world”可能只是一个字符,但这个字符在键盘上根本找不到。 - Michael Mrozek
显示剩余2条评论

11

验证电子邮件地址并没有什么实际帮助。因为常见的打字错误或编造的电子邮件地址看起来在语法上也是有效的,所以这种验证方法无法捕获这些情况。

如果你想确保一个地址是有效的,你只有发送确认邮件的选择。

如果你只是想确保用户输入的内容类似于电子邮件而不是"asdf"之类的东西,那么只需检查是否包含@符号即可。更复杂的验证并没有提供任何好处。

(我知道这并没有回答你的问题,但我认为值得提一下)


3
我认为它回答了这个问题。 - bzlm
我还喜欢检查电子邮件地址中只有一个@字符,并且它不是第一个或最后一个字符。当我知道电子邮件地址将是“通常”格式的电子邮件地址(即UserName@DomainName.com)时,我还喜欢检查@字符后面是否有1个或多个字符,然后是一个“点”字符,再后面至少有1个或多个字符。 - Adam Porad
2
@Adam:如果你选择这条路,就必须做到正确。请参考janm的解释,了解如何在有效的电子邮件地址中使用多个@符号。 - JacquesB

8

在BNF中有一种上下文无关文法,用于描述RFC-2822中有效的电子邮件地址。它非常复杂。例如:

" @ "@example.com

这是一个有效的电子邮件地址。我不知道有什么正则表达式可以完全匹配,通常给出的示例需要先去除注释。我曾经编写了一个递归下降解析器来完全匹配它。


8

我现已整理了来自Cal Henderson、Dave Child、Phil Haack、Doug Lovell和RFC 3696的测试用例。共有158个测试地址。

我将这些测试用例对所有我能找到的验证器进行了测试,并将比较结果发布在这里:http://www.dominicsayers.com/isemail

我将尝试保持该页面的最新状态,以便人们增强其验证器。感谢Cal、Dave和Phil在编制这些测试用例、对我的验证器进行建设性批评方面的帮助和合作。

人们应该注意RFC 3696中的勘误,特别是其中三个规范示例实际上是无效地址。地址的最大长度为254或256个字符,而不是320个字符。


7

虽然允许使用'+'等字符可能对于打击垃圾邮件的用户非常有用,例如:myemail+sketchysite@gmail.com (即时丢弃的Gmail地址),但并非所有情况都适用。


这是相当普遍的,不仅在 Gmail 上;我已经这样做了大约十年(我使用 - 而不是 +,因为我更喜欢它,而且这是我的服务器,所以我可以这样做,但 + 是正常的)。 - Mark Baker

6
接受奇怪、不常见的电子邮件地址格式,是否取决于一个人想要用它们做什么,这是我的观点。
如果你正在编写邮件服务器,你必须非常精确和极其正确地接受输入。因此,“疯狂”的正则表达式如上所述是合适的。
对于我们其他人来说,我们主要只关心确保用户在网页表单中输入的内容看起来合理,并且没有一些 SQL 注入或缓冲区溢出等问题。
坦白地说,当注册邮件列表、新闻通讯或网站时,是否真的有人在乎让某人输入一个带有注释、换行符、引号、空格、括号或其他无意义字符的 200 字符电子邮件地址?对这种小丑的适当回应是“当你有一个像用户名@域名.tld 这样的地址时,请再回来”。
我进行的验证包括确保有且仅有一个“@”;没有空格、空值或换行符;在“@”右侧的部分至少有一个点(但不是连续两个点);并且没有引号、括号、逗号、冒号、感叹号、分号或反斜杠,所有这些都更可能是黑客攻击而不是实际电子邮件地址的一部分。
是的,这意味着我拒绝了某些人可能尝试在我的网站上注册的有效地址 - 也许我“错误地”拒绝了真实世界中多达 0.001% 的地址!但我可以接受这一点。

4

引用和RFC的其他很少使用但有效的部分使其变得困难。我对这个话题了解不够,无法做出明确的评论,除了“它很难” - 但幸运的是,其他人已经 详细地写过它。

至于它的有效正则表达式,Perl Mail :: Rfc822 :: Address模块包含 一种似乎有效的正则表达式 - 但仅当任何注释已被替换为空格时才有效。 (电子邮件地址中有注释?你看到为什么比人们预期的要困难...)

当然,在其他地方广泛存在的简化的正则表达式将验证几乎所有真正使用的电子邮件地址...


1
什么?Jon Skeet的回答得分为0?荒谬。 - Richard J. Ross III

3

在Java中检查电子邮件地址的简单有效方法是使用Apache Commons Validator库中的EmailValidator。

在发送电子邮件之前,我总是会将输入表单中的电子邮件地址检查一遍,即使只能捕获一些打字错误。您可能不希望编写自动扫描“投递失败”通知邮件的程序。 :-)


3

一些正则表达式的变种可以匹配嵌套括号(例如,兼容Perl的正则表达式)。尽管如此,我曾见过一个正则表达式声称能够正确匹配RFC 822,但它是两页没有任何空格的文本。因此,检测有效电子邮件地址的最好方法是向其发送邮件并查看其是否有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接