如何使用正则表达式验证电子邮件地址?

4125
多年来,我已经逐渐开发出一种正则表达式,可以正确验证大多数电子邮件地址(假设它们没有使用IP地址作为服务器部分)。我在几个PHP程序中使用它,大多数情况下都有效。但是,不时有人联系我,称使用该表达式的站点出现问题,我最近意识到我没有允许四个字符的TLDs,因此不得不进行一些调整。你认为验证电子邮件的最佳正则表达式是什么?我看过几种解决方案,它们使用函数,这些函数使用几个较短的表达式,但我宁愿在一个简单的函数中使用一个长且复杂的表达式,而不是在一个更复杂的函数中使用几个短的表达式。

10
可以验证 IDNA 格式是否正确的正则表达式太长了,无法在 StackExchange 中使用。(规范化的规则非常复杂,特别不适合使用正则表达式处理。) - Jasen
正则表达式可能是可变的,因为在某些情况下,电子邮件内容可能包含空格,而在其他情况下,则不能包含任何空格。 - Ṃųỻịgǻňạcểơửṩ
我建议您查看这篇文章:https://debounce.io/blog/articles/email-syntax-error-explained/ - Iman
显示剩余7条评论
80个回答

7
public bool ValidateEmail(string sEmail)
{
    if (sEmail == null)
    {
        return false;
    }

    int nFirstAT = sEmail.IndexOf('@');
    int nLastAT = sEmail.LastIndexOf('@');

    if ((nFirstAT > 0) && (nLastAT == nFirstAT) && (nFirstAT < (sEmail.Length - 1)))
    {
        return (Regex.IsMatch(sEmail, @"^[a-z|0-9|A-Z]*([_][a-z|0-9|A-Z]+)*([.][a-z|0-9|A-Z]+)*([.][a-z|0-9|A-Z]+)*(([_][a-z|0-9|A-Z]+)*)?@[a-z][a-z|0-9|A-Z]*\.([a-z][a-z|0-9|A-Z]*(\.[a-z][a-z|0-9|A-Z]*)?)$"));
    }
    else
    {
        return false;
    }
}

有时会失败;如果用户在电子邮件地址中包含引号字符串内的“@”字符,则会出现问题。 - awwright

7

我仍在使用:

^[A-Za-z0-9._+\-\']+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}$

但是随着IPv6和Unicode的出现,也许这是最好的选择:

console.log(/^[\p{L}!#-'*+\-/\d=?^-~]+(.[\p{L}!#-'*+\-/\d=?^-~])*@[^@\s]{2,}$/u.test("תה.בועות@.fm"))

Gmail允许使用连续的点,但Microsoft Exchange Server 2007不允许,这符合我了解到的最新标准


不允许使用"John Smith"@example.com - David Conrad
真的吗?但那到底是什么时候需要呢? - Cees Timmerman
2
电子邮件地址中有空格时怎么办? - David Conrad
我从未见过有人真正使用它,而且我认为官方规格说明它仅用于向后兼容。 - Cees Timmerman
1
@DavidConrad 您的意思是根据此评论,应该使用"John\ Smith"@example.com - Cees Timmerman
显示剩余2条评论

7
我使用多步骤验证。由于没有完美的方法可以验证电子邮件地址,因此不能制作完美的方法,但至少可以通知用户他/她正在做错事-这是我的方法:
1. 我首先使用非常基本的正则表达式进行验证,只检查电子邮件是否包含一个@符号,并且在该符号之前或之后不为空。例如:/^[^@\s]+@[^@\s]+$/ 2. 如果第一个验证器未通过(对于大多数地址而言,它应该通过,尽管不是完美的),则警告用户该电子邮件无效,并不允许他/她继续输入。
3. 如果通过了第一步骤,则根据更严格的正则表达式进行验证-可能会禁止有效的电子邮件。如果未通过,则向用户发出可能存在错误的警告,但是允许用户继续。与步骤(1)不同,其中用户不能继续,因为这是明显的错误。
换句话说,第一个自由验证仅用于剥离明显的错误,并将其视为“错误”。人们会输入空地址、没有@符号等。这应被视为一个错误。第二个验证更严格,但被视为“警告”,并允许用户继续输入,但警告至少要检查他/她是否输入了有效条目。这里的关键在于错误/警告方法-错误是无论如何都不可能是有效电子邮件的东西。
当然,您可以调整使第一个正则表达式更自由,第二个正则表达式更严格的内容。
根据您的需求,上述方法可能适合您。

从技术上讲,电子邮件可以包含多个@符号。最近我偶然发现了这个令人惊奇的怪异现象。例如:"very.(),:;<>[]".VERY."very@\ "very".unusual"@strange.example.com - Allan Deamon
1
同意,但我从来没有声称我的方法是100%的绝对可靠。它适用于大多数情况。在某些时候,你必须要保持现实,并且舍弃那些可能性极小的情况。大多数电子邮件地址都是something@something.something的格式。如果有人选择使用一种最自由的语法来创建电子邮件地址,那么他/她将会面临各种与服务器/客户端程序不兼容、无法正确验证或允许此类电子邮件的问题,或者在发送/接收时根本无法正常工作。那时这样的用户将被迫使用更“标准”的语法来确保电子邮件在任何地方都能够正常工作。 - Coder12345

7

电子邮件地址的正则表达式为:

/^("(?:[!#-\[\]-\u{10FFFF}]|\\[\t -\u{10FFFF}])*"|[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*)@([!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*|\[[!-Z\^-\u{10FFFF}]*\])$/u

这个正则表达式与非过时电子邮件地址的addr-specABNF完全相同,如RFC 5321, RFC 5322,和RFC 6532中所指定。此外,您还需要验证以下内容:
- 电子邮件地址是UTF-8格式(如果无法发送到国际化电子邮件地址,则为ASCII)。 - 地址不超过320个UTF-8字节。 - 用户部分(第一个匹配组)不超过64个UTF-8字节。 - 域名部分(第二个匹配组)不超过255个UTF-8字节。

最简单的方法是使用现有的函数。在PHP中,可以使用filter_var函数,使用FILTER_VALIDATE_EMAILFILTER_FLAG_EMAIL_UNICODE(如果您可以发送到国际化电子邮件地址):

$email_valid = filter_var($email_input, FILTER_VALIDATE_EMAIL, FILTER_FLAG_EMAIL_UNICODE);

然而,也许你正在构建这样一个功能——实现这个最简单的方法是使用正则表达式。

请记住,这仅验证电子邮件地址不会导致语法错误。验证地址是否能够接收电子邮件的唯一方法是实际地发送一封电子邮件。

接下来,我将介绍如何生成这个正则表达式。


我写了一个新答案,因为这里大多数答案都犯了一个错误,要么指定了一个太严格的模式(因此不再适用);要么呈现了一个实际上匹配MIME消息头而不是电子邮件地址本身的正则表达式。

完全可以从ABNF制作正则表达式,只要没有递归部分。

RFC 5322指定在MIME消息中发送什么是合法的;将其视为合法电子邮件地址的上限。

但是,严格按照这个ABNF进行操作是错误的:该模式技术上表示如何在MIME消息中编码电子邮件地址,并允许不属于电子邮件地址的字符串,例如折叠空格和注释;它还包括不合法生成的过时形式的支持(但服务器出于历史原因会读取)。 电子邮件地址不包括这些内容。

RFC 5322解释:

“atom”和“dot-atom”都被解释为单个单位,包括构成它的字符串。从语义上讲,其余字符周围的可选注释和FWS不是该原子的一部分;原子只是原子中atext字符的运行或者dot-atom中atext和“.”字符的运行。

在某些定义中,将会有以“obs-”开头的非终结符。这些“obs-”元素指的是第4节中定义的过时语法中的标记。在所有情况下,这些产生式应被忽略,以生成合法的Internet消息,并且不能作为此类消息的一部分使用。

如果从RFC 5322的addr-spec中删除CFWSBWSobs-*规则,并对结果进行一些优化(我使用了"greenery"),则可以生成此正则表达式,用斜杠引用并锚定(适用于ECMAScript和兼容方言,为了清晰起见添加了换行符):

/^("(?:[!#-\[\]-~]|\\[\t -~])*"|[!#-'*+\-/-9=?A-Z\^-~](?:\.?[!#-'*+\-/-9=?A-Z\^-~])*)
@([!#-'*+\-/-9=?A-Z\^-~](?:\.?[!#-'*+\-/-9=?A-Z\^-~])*|\[[!-Z\^-~]*\])$/

这仅支持ASCII电子邮件地址。要支持RFC 6532国际化电子邮件地址,请将~字符替换为\u{10FFFF}(PHP、带有u标志的ECMAScript),或\uFFFF(适用于UTF-16实现,如.NET和旧版ECMAScript / JavaScript):

/^("(?:[!#-\[\]-\u{10FFFF}]|\\[\t -\u{10FFFF}])*"|[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*)@([!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*|\[[!-Z\^-\u{10FFFF}]*\])$/u

这是可行的,因为我们使用的ABNF不是递归的,因此形成了一个非递归的正则语法,可以转换为正则表达式。
它的分解如下:
  • 用户部分(在@之前)可以是点原子或带引号的字符串。
  • "([!#-\[\]-~]|\\[\t -~])*" 指定了用户的带引号字符串形式,例如 "root@home"@example.com。它允许双引号内的任何非控制字符;除了空格、制表符、双引号和反斜杠必须被反斜杠转义。
  • [!#-'*+\-/-9=?A-Z\^-~] 是用户的点原子的第一个字符。
  • (\.?[!#-'*+\-/-9=?A-Z\^-~])* 匹配剩余的点原子,允许使用点(除了在另一个点后面或作为最后一个字符)。
  • @ 表示域名。
  • 域名部分可以是点原子或域字面量。
  • [!#-'*+\-/-9=?A-Z\^-~](\.?[!#-'*+\-/-9=?A-Z\^-~])* 与上述相同的点原子形式,但此处表示域名和IPv4地址。
  • \[[!-Z\^-~]*\] 将匹配IPv6地址和主机名的未来定义。
这个正则表达式允许所有符合规范的电子邮件地址,并可以直接在MIME消息中使用(除了行长度限制外,此时需要添加折叠空格)。
此外,它设置了非捕获组,使得match[1]是用户,match[2]是主机。(但是如果match[1]以双引号开头,则过滤掉反斜杠转义、开始和结束的双引号:"root"@example.comroot@example.com标识相同的收件箱。)
最后,请注意RFC 5321设置了电子邮件地址的长度限制。用户部分最多可以有64个字节,域名部分最多可以有255个字节。包括@字符,在UTF-8编码后,整个地址的限制为320个字节。这是以字节为单位衡量的,而不是字符。
请注意,RFC 5322 ABNF定义了一种宽容的域名语法,允许使用当前被认为是无效的名称。这也允许出现在未来可能合法的域名。这不应该成为问题,因为处理方式与不存在的域名相同。
始终考虑到用户可能会输入一个有效的电子邮件地址,但他们没有访问权限。验证电子邮件地址的唯一可靠方法是发送电子邮件。
这是我文章中的内容改编自E-Mail Addresses & Syntax

1
我可以在JavaScript中使用它,但无法将其格式化为C#使用。我尝试将其放入regex101网站,但它显示无效。 - Post Impatica
1
@PostImpatica 究竟是什么错误?Regex101期望一个斜杠分隔的正则表达式。我不知道C#需要哪种方言。如果你的方言是斜杠分隔的,你需要用反斜杠转义斜杠。 - awwright
1
"John Smith"@example.com 在 regexr.com 上无法正常工作。 - Cees Timmerman
2
@CeesTimmerman 在引用形式中必须转义空格,请参见 https://www.rfc-editor.org/rfc/rfc5322#section-3.2.4我的帖子中提到了这一点:“它允许在双引号内使用任何非控制字符;除了空格、制表符、双引号和反斜杠必须进行反斜杠转义。”请注意,ASCII中的空白字符不被视为“可打印字符”,请参见 https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1 中的VCHAR生产。 - awwright

6
我不相信 bortzmeyer提出的主张,即“文法(在RFC 5322中指定)过于复杂,不能由正则表达式处理”。
以下是文法(来自3.4.1. Addr-Spec Specification):
addr-spec       =   local-part "@" domain
local-part      =   dot-atom / quoted-string / obs-local-part
domain          =   dot-atom / domain-literal / obs-domain
domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
dtext           =   %d33-90 /          ; Printable US-ASCII
                    %d94-126 /         ;  characters not including
                    obs-dtext          ;  "[", "]", or "\"

假设dot-atom、quoted-string、obs-local-part和obs-domain本身就是正则语言,那么这是一个非常简单的语法。只需将addr-spec生成式中的local-part和domain替换为它们各自的生成式,就可以得到一个正则语言,可以直接转换为正则表达式。

5
在你开始做出任何假设之前,你应该调查一下CFWS。这是一个噩梦。 - rjbs
CFWS = (1*([FWS] comment) [FWS]) / FWS。然而,我并没有看到任何使语言不规则的规则。它肯定很复杂,但是一个复杂的正则表达式仍然可以处理它。 - Dimitris Andreou
3
这并没有回答问题,而是对另一个回答做出的回应。 - Luna
CFWS不是电子邮件地址的一部分,它是MIME语法的一部分。请参阅我的答案https://dev59.com/uHVC5IYBdhLWcg3wtzut#63841473以了解原因。 - awwright

6

我知道这个问题是关于正则表达式的,但是我猜想阅读这些解决方案的90%开发人员都在尝试验证在浏览器中显示的HTML表单中的电子邮件地址。

如果是这种情况,我建议使用新的HTML5 <input type="email"> 表单元素:

HTML5:

 <input type="email" required />

CSS 3:

 input:required {
      background-color: rgba(255, 0, 0, 0.2);
 }

 input:focus:invalid {
     box-shadow: 0 0 1em red;
     border-color: red;
 }

 input:focus:valid {
     box-shadow: 0 0 1em green;
     border-color: green;
 }

这是在HTML5表单验证无需JS - JSFiddle - 代码游乐场

这有几个优点:

  1. 自动验证,无需自定义解决方案:简单易于实现。
  2. 无需JavaScript,并且如果禁用了JavaScript,则没有问题。
  3. 没有服务器需要计算任何内容。
  4. 用户可以立即获得反馈。
  5. 旧浏览器应自动回退到输入类型 "文本"
  6. 移动浏览器可以显示专门的键盘(@-Keyboard)
  7. 使用CSS 3非常容易进行表单验证反馈

显而易见的缺点可能是老式浏览器的验证丢失,但这将随着时间的推移而改变。 我宁愿选择这个而不是那些疯狂的正则表达式杰作。

另请参见:


另一个缺点是这只是客户端的。对于提供流畅的用户体验很好,但对于验证数据来说不太好。 - acrosman
默认电子邮件验证的问题在于它有很多误报。您需要使用我的完整模式来消除所有误报,同时防止假阴性的出现。该模式可以通过pattern属性添加。有关更多信息,请参见我的帖子 - Joeytje50

5

这个规则匹配的是我们的Postfix服务器无法发送的内容。

允许使用字母、数字、-、_、+、.、&、/和!

不允许-foo@bar.com

不允许asd@-bar.com

/^([a-z0-9\+\._\/&!][-a-z0-9\+\._\/&!]*)@(([a-z0-9][-a-z0-9]*\.)([-a-z0-9]+\.)*[a-z]{2,})$/i

5

对于PHP,我使用Nette框架的电子邮件地址验证器:(点击此处查看源代码)

/* public static */ function isEmail($value)
{
    $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
    $localPart = "(?:\"(?:[ !\\x23-\\x5B\\x5D-\\x7E]*|\\\\[ -~])+\"|$atom+(?:\\.$atom+)*)"; // Quoted or unquoted
    $alpha = "a-z\x80-\xFF"; // Superset of IDN
    $domain = "[0-9$alpha](?:[-0-9$alpha]{0,61}[0-9$alpha])?"; // RFC 1034 one domain component
    $topDomain = "[$alpha](?:[-0-9$alpha]{0,17}[$alpha])?";
    return (bool) preg_match("(^$localPart@(?:$domain\\.)+$topDomain\\z)i", $value);
}

5

对于我来说,正确检查电子邮件地址的方法是:

  1. 检查@符号是否存在,并且它之前和之后有一些非@符号:/^[^@]+@[^@]+$/
  2. 尝试向此地址发送带有某个“激活代码”的电子邮件。
  3. 当用户“激活”他/她的电子邮件地址时,我们将看到所有的内容都是正确的。

当然,你可以在前端显示一些警告或工具提示,以帮助用户避免常见错误,比如域名部分没有点或名称中有空格但没有引用等。但是如果用户确实需要输入“hello@world”,则必须接受该地址。

此外,需要记住电子邮件地址标准曾经发生过变化,可能还会不断地演变,因此不能只使用一次“标准有效”的正则表达式。同时,需要记住一些具体的互联网服务器可能会有一些细节上的问题,这些与共同使用的标准不同,事实上采用了自己修改过的标准。

因此,只需检查@,在前端提示用户并向给定的地址发送验证电子邮件即可。


4
这是一种用于电子邮件的正则表达式之一:
^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$

看起来像是线路噪音。你有解释和/或参考资料吗? - Peter Mortensen

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