快速入門
教學
工具和語言
範例
參考
書籍評論
範例
正規表示式範例
數字範圍
浮點數
電子郵件地址
IP 位址
有效日期
數字日期轉文字
信用卡號碼
比對完整行
刪除重複行
程式設計
兩個相近的字詞
陷阱
災難性的回溯
過多重複
拒絕服務
讓所有內容都可選
重複擷取群組
Unicode 和 8 位元混合
本網站的更多資訊
簡介
正規表示式快速入門
正規表示式教學
替換字串教學
應用程式和語言
正規表示式範例
正規表示式參考
替換字串參考
書籍評論
可列印 PDF
關於本網站
RSS Feed 和部落格
RegexMagic—Generate regular expressions matching email addresses

如何尋找或驗證電子郵件地址

我收到最多回饋的正規表示式,更別提「錯誤」回報,就是您會在本網站的首頁上找到的這個:\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b。我聲稱這個正規表示式可以比對任何電子郵件地址。我收到的回饋大多反駁這個說法,並指出這個正規表示式無法比對的電子郵件地址。通常,「錯誤」回報也會包含建議,讓這個正規表示式變得「完美」。

正如我在下方說明的,我的說法只有在我接受對有效電子郵件地址的定義,以及什麼不是有效電子郵件地址的定義時,才成立。如果您想使用不同的定義,您必須調整正規表示式。比對有效的電子郵件地址是一個完美的範例,說明 (1) 在撰寫正規表示式之前,您必須確切知道您要比對什麼,以及什麼不要比對;(2) 精確性和實用性之間通常會有取捨。

上述正規表示式的優點在於它可以比對當今使用中的 99% 電子郵件地址。它比對的所有電子郵件地址都可以由 99% 的所有電子郵件軟體處理。如果您正在尋找快速解決方案,您只需要閱讀下一段落。如果您想了解所有取捨,並從中選擇大量替代方案,請繼續閱讀。

如果您想使用上述正規表示式,您需要了解兩件事。首先,長正規表示式會讓段落難以正確格式化。因此,我在三個字元類別中都沒有包含 a-z。此正規表示式旨在與您的正規表示式引擎的「不分大小寫」選項一起使用。(您會訝異我收到多少關於此的「錯誤」報告。)其次,上述正規表示式以 字元邊界 為界定,這使其適用於從檔案或較大區塊文字中萃取電子郵件地址。如果您想檢查使用者是否輸入有效的電子郵件地址,請將字元邊界替換為 字串開頭和字串結尾錨點,如下所示:^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$

前一段落也適用於以下所有範例。您可能需要將字元邊界變更為字串開頭/結尾錨點,或反之亦然。而且您必須開啟不分大小寫比對選項。

驗證電子郵件地址的取捨

在 ICANN 讓任何資金雄厚的公司都能建立自己的頂層網域之前,最長的頂層網域是鮮少使用的 .museum.travel,長度為 6 個字母。最常見的頂層網域長度為 2 個字母,用於特定國家的網域,以及 3 或 4 個字母,用於一般用途的網域,例如 .com.info。您會在各種正規表示式教學課程和參考中找到許多用於驗證電子郵件地址的正規表示式,這些正規表示式仍然假設頂層網域相當短。較舊版本的此正規表示式教學課程在引言中提到 \b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b 作為電子郵件地址的正規表示式。這個正規表示式和本頁頂端的正規表示式只有一個小差異。正規表示式結尾的 4 將頂層網域限制為 4 個字元。如果您使用這個正規表示式和錨點來驗證訂單表單中輸入的電子郵件地址,fabio@disapproved.solutions 就必須到其他地方購物。是的,.solutions TLD 存在,而我在撰寫本文時,disaproved.solutions 每年售價 16.88 美元。

如果您想對頂層網域比 [A-Z]{2,} 更加嚴格,^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}$ 是您實際上能達到的極限。網域名稱的每個部分長度不得超過 63 個字元。沒有單一數字的頂層網域,也沒有任何頂層網域包含數字。看起來 ICANN 也不會核准此類網域。

電子郵件地址可以位於子網域的伺服器上,如 john@server.department.company.com。由於我在 @ 符號後的字元類別中包含一個點,因此所有上述正規表示式都符合此電子郵件地址。但上述正規表示式也符合 john@aol...com,由於連續的點,因此無效。你可以透過在上述任何正規表示式中將 [A-Z0-9.-]+\. 替換為 (?:[A-Z0-9-]+\.)+ 來排除此類符合項。我從字元類別中移除點,而重複使用字元類別和後面的文字點。例如,^[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,}$ 符合 john@server.department.company.com,但不符合 john@aol...com

如果你想避免系統因任意大的輸入而中斷,你可以將無限 量詞 替換為有限量詞。^[A-Z0-9._%+-]{1,64}@(?:[A-Z0-9-]{1,63}\.){1,125}[A-Z]{2,63}$ 考量到本地端 (在 @ 之前) 限制為 64 個字元,而網域名稱的每個部分限制為 63 個字元。子網域的數量沒有直接限制。但 SMTP 可以處理的電子郵件地址最大長度為 254 個字元。因此,如果本地端為單一字元、頂層網域為兩個字元,且子網域為單一字元,則子網域的最大數量為 125。

The previous regex does not actually limit email addresses to 254 characters. If each part is at its maximum length, the regex can match strings up to 8129 characters in length. You can reduce that by lowering the number of allowed sub-domains from 125 to something more realistic like 8. I’ve never seen an email address with more than 4 subdomains. If you want to enforce the 254 character limit, the best solution is to check the length of the input string before you even use a regex. Though this requires a few lines of procedural code, checking the length of a string is near-instantaneous. If you can only use regexes, ^[A-Z0-9@._%+-]{6,254}$ can be used as a first pass to make sure the string doesn’t contain invalid characters and isn’t too short or too long. If you need to do everything with one regex, you’ll need a regex flavor that supports lookahead. The regular expression ^(?=[A-Z0-9@._%+-]{6,254}$)[A-Z0-9._%+-]{1,64}@(?:[A-Z0-9-]{1,63}\.){1,8}[A-Z]{2,63}$ uses a lookahead to first check that the string doesn’t contain invalid characters and isn’t too short or too long. When the lookahead succeeds, the remainder of the regex makes a second pass over the string to check for proper placement of the @ sign and the dots.

這些正則表示式都允許在本地端部分的任何地方使用字元 ._%+-。你可以使用 ^[A-Z0-9][A-Z0-9._%+-]{0,63} 來強制本地端部分以字母開頭,而不是使用 ^[A-Z0-9._%+-]{1,64} 作為本地端部分:^[A-Z0-9][A-Z0-9._%+-]{0,63}@(?:[A-Z0-9-]{1,63}\.){1,125}[A-Z]{2,63}$。在使用向前預查來檢查地址的整體長度時,可以在向前預查中檢查第一個字元。在檢查本地端部分的長度時,我們不需要重複初始字元檢查。這個正則表示式太長了,不適合頁面的寬度,所以讓我們開啟自由間距模式

^(?=[A-Z0-9][A-Z0-9@._%+-]{5,253}$)
[A-Z0-9._%+-]{1,64}@(?:[A-Z0-9-]{1,63}\.){1,8}[A-Z]{2,63}$

網域名稱可以包含連字號。但它們不能以連字號開頭或結尾。[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])? 匹配長度在 1 到 63 個字元之間且以字母或數字開頭和結尾的網域名稱。非擷取群組使網域名稱中間和最後一個字母或數字作為一個整體是可選的,以確保我們允許單字元網域名稱,同時確保包含兩個或多個字元的網域名稱不會以連字號結尾。整體正則表示式開始變得相當複雜

^[A-Z0-9][A-Z0-9._%+-]{0,63}@
(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.){1,8}[A-Z]{2,63}$

Domain names cannot contain consecutive hyphens. [A-Z0-9]+(?:-[A-Z0-9]+)* matches a domain name that starts and ends with a letter or digit and that contains any number of non-consecutive hyphens. This is the most efficient way. This regex does not do any backtracking to match a valid domain name. It matches all letters and digits at the start of the domain name. If there are no hyphens, the optional group that follows fails immediately. If there are hyphens, the group matches each hyphen followed by all letters and digits up to the next hyphen or the end of the domain name. We can’t enforce the maximum length when hyphens must be paired with a letter or digit, but letters and digits can stand on their own. But we can use the lookahead technique that we used to enforce the overall length of the email address to enforce the length of the domain name while disallowing consecutive hyphens: (?=[A-Z0-9-]{1,63}\.)[A-Z0-9]+(?:-[A-Z0-9]+)*. Notice that the lookahead also checks for the dot that must appear after the domain name when it is fully qualified in an email address. This is important. Without checking for the dot, the lookahead would accept longer domain names. Since the lookahead does not consume the text it matches, the dot is not included in the overall match of this regex. When we put this regex into the overall regex for email addresses, the dot will be matched as it was in the previous regexes:

^[A-Z0-9][A-Z0-9._%+-]{0,63}@
(?:(?=[A-Z0-9-]{1,63}\.)[A-Z0-9]+(?:-[A-Z0-9]+)*\.){1,8}[A-Z]{2,63}$

如果我們包含前瞻來檢查整體長度,我們的正規表示式會在本地端部分執行兩次,在網域名稱執行三次,以驗證所有內容

^(?=[A-Z0-9][A-Z0-9@._%+-]{5,253}$)[A-Z0-9._%+-]{1,64}@
(?:(?=[A-Z0-9-]{1,63}\.)[A-Z0-9]+(?:-[A-Z0-9]+)*\.){1,8}[A-Z]{2,63}$

在現代的個人電腦或伺服器上,當驗證單一 254 字元的電子郵件地址時,這個正規表示式將執行得很好。拒絕較長的輸入會更快,因為當先行斷言在第一次通過時失敗時,正規表示式就會失敗。但我不會建議使用像這樣複雜的正規表示式來透過大量文件或信件存檔來搜尋電子郵件地址。你最好使用此頁面頂端的簡單正規表示式來快速收集看起來像電子郵件地址的所有內容。對結果進行重複資料刪除,然後如果你想進一步篩選出無效的地址,請使用更嚴格的正規表示式。

說到回溯,此頁面上的正規表示式都沒有進行任何回溯來比對有效的電子郵件地址。但特別是後者,可能會對不是有效電子郵件地址的東西進行相當多的回溯。如果你的正規表示式風格支援佔有量詞,你可以透過讓所有量詞都變成佔有量詞來消除所有回溯。因為不需要回溯來尋找比對,所以這樣做不會改變這些正規表示式比對的內容。它只允許它們在輸入不是有效的電子郵件地址時更快地失敗。正確處理次網域的最簡單正規表示式隨後變成 ^[A-Z0-9._%+-]++@(?:[A-Z0-9-]++\.)++[A-Z]{2,}+$,在每個量詞後加上一個額外的 +。我們可以對我們最複雜的正規表示式執行相同的操作

^(?=[A-Z0-9][A-Z0-9@._%+-]{5,253}+$)[A-Z0-9._%+-]{1,64}+@
(?:(?=[A-Z0-9-]{1,63}+\.)[A-Z0-9]++(?:-[A-Z0-9]++)*+\.){1,8}+[A-Z]{2,63}+$

所有這些正規表示式中的一個重要取捨是,它們只允許英文字母、數字和最常用的特殊符號。主要原因是我不相信我所有的電子郵件軟體都能處理其他內容。即使 John.O'Hara@theoharas.com 是語法上有效的電子郵件地址,但有些軟體可能會誤將撇號解釋為分隔符號。例如,將此電子郵件地址盲目插入 SQL 查詢中,最好的情況是當字串以單引號分隔時會導致失敗,最壞的情況是會讓你的網站面臨 SQL 注入攻擊。

當然,網域名稱已經可以包含非英文字元很多年了。但大多數軟體仍然堅持西方程式設計師習慣使用的 37 個字元。支援國際化網域會開啟一個問題,也就是非 ASCII 字元應該如何編碼。因此,如果你使用此頁面上的任何正規表示式,任何具有 @ทีเอชนิค.ไทย 地址的人都會很倒楣。但或許可以說明的是,http://ทีเอชนิค.ไทย 會直接重新導向到 http://thnic.co.th,即使它們的業務是銷售 .ไทย 網域。

結論是,要決定使用哪個正規表示式,無論你是要比對電子郵件地址或其他定義模糊的東西,你都需要先考慮所有取捨。比對到無效的東西有多糟?不比對到有效的東西有多糟?你的正規表示式可以有多複雜?如果你必須在稍後變更正規表示式,因為它太廣泛或太狹窄,那會有多昂貴?這些問題的不同答案將需要不同的正規表示式作為解決方案。我的電子郵件正規表示式可以滿足我的需求,但它可能無法滿足你的需求。

正規表示式不會傳送電子郵件

不要過度嘗試使用正規表示法來消除無效的電子郵件地址。原因在於,在嘗試寄送電子郵件之前,你並不知道一個地址是否有效。而且,即使如此,這可能還不夠。即使電子郵件已送達某個信箱,這並不表示有人會讀取該信箱。如果你真的需要確定某個電子郵件地址是否有效,你必須寄送一封電子郵件給該地址,其中包含一個代碼或連結,讓收件者執行第二個驗證步驟。如果你這麼做,那麼使用可能會拒絕有效電子郵件地址的正規表示法就沒有什麼意義了。

相同的原則適用於許多情況。在嘗試比對有效日期時,通常使用一點算術來檢查閏年會比較容易,而不是嘗試在正規表示法中執行此操作。使用正規表示法來尋找潛在的比對或檢查輸入是否使用正確的語法,並對正規表示法傳回的潛在比對執行實際驗證。正規表示法是一個強大的工具,但它們遠非萬靈丹。

官方標準:RFC 5322

你可能想知道為什麼沒有「官方」的萬無一失正規表示法來比對電子郵件地址。嗯,有一個官方定義,但它並非萬無一失。

官方標準稱為RFC 5322。它描述了有效電子郵件地址必須遵守的語法。你可以(但你不應該這麼做,請繼續閱讀)使用以下正規表示法來實作它。RFC 5322 讓網域名稱部分開放給實作特定的選擇,這些選擇在今日的網際網路上無法運作。正規表示法實作了RFC 1035中的「首選」語法,這是 RFC 5322 中的建議之一。

\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*
 
|  "(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]
      
|  \\[\x01-\x09\x0b\x0c\x0e-\x7f])*")
@ (?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?
  
|  \[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
       
(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:
          
(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]
          
|  \\[\x01-\x09\x0b\x0c\x0e-\x7f])+)
     
\])\z

此正規表示法分為兩部分:@ 符號之前的部分和 @ 符號之後的部分。@ 符號之前的部分有兩個選項。第一個選項允許它包含一系列字母、數字和某些符號,包括一個或多個點。但是,點不能連續出現,也不能出現在電子郵件地址的開頭或結尾。另一個選項要求 @ 符號之前的部分用雙引號括起來,允許在引號之間使用任何 ASCII 字元串。空白字元、雙引號和反斜線必須用反斜線進行跳脫。

@ 符號之後的部分也有兩個選項。它可以是完全限定的網域名稱(例如 regular-expressions.info),或者可以是方括號中的文字網際網路地址。文字網際網路地址可以是 IP 地址,也可以是特定網域的路由地址。

不應使用此正規表示法的原因是它過於廣泛。您的應用程式可能無法處理此正規表示法允許的所有電子郵件地址。特定網域的路由地址可能包含不可列印的 ASCII 控制字元,如果您的應用程式需要顯示地址,這可能會造成問題。並非所有應用程式都支援使用雙引號或方括號的本地部分語法。事實上,RFC 5322 本身將使用方括號的表示法標記為過時。

如果我們省略 IP 地址、特定網域地址、使用雙引號和方括號的語法,我們將獲得更實用的 RFC 5322 實作。它仍然會符合當今實際使用中 99.99% 的所有電子郵件地址。

\A[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@
(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z

這兩個正規表示法都不對整體電子郵件地址、本地部分或網域名稱強制長度限制。RFC 5322 沒有指定任何長度限制。這些限制源於其他協定的限制,例如實際傳送電子郵件的 SMTP 協定。RFC 1035 確實指出網域必須為 63 個字元或更少,但沒有將其包含在其語法規範中。原因是真正的正規語言無法同時強制長度限制和禁止連續連字號。但現代正規表示法並非真正的正規表示法,因此我們可以使用前瞻檢查來新增長度限制檢查,就像我們之前所做的那樣

\A(?=[a-z0-9@.!#$%&'*+/=?^_`{|}~-]{6,254}\z)
 
(?=[a-z0-9.!#$%&'*+/=?^_`{|}~-]{1,64}@)
 
[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*
@ (?:(?=[a-z0-9-]{1,63}\.)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+
  
(?=[a-z0-9-]{1,63}\z)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\z

因此,即使遵循官方標準,仍然需要做出取捨。不要盲目從線上程式庫或討論論壇複製正規表示法。務必在您自己的資料和應用程式上測試它們。