Wyrażenie regularne, na temat którego otrzymuję najwięcej informacji zwrotnych, nie wspominając o raportach o błędach, jest tym, które znajdziesz na stronie głównej tej witryny: \B+@@@@@. To wyrażenie regularne, jak twierdzę, pasuje do każdego adresu e-mail. Większość informacji zwrotnych, które otrzymuję, obala to twierdzenie, pokazując jeden adres e-mail, do którego to wyrażenie nie pasuje. Zazwyczaj raport „błędu” zawiera również sugestię, jak uczynić to wyrażenie „doskonałym”.
Jak wyjaśniam poniżej, moje twierdzenie jest prawdziwe tylko wtedy, gdy przyjmiemy moją definicję tego, czym naprawdę jest poprawny adres e-mail, a czym nie jest. Jeśli chcesz użyć innej definicji, będziesz musiał dostosować regex. Dopasowywanie poprawnego adresu email jest doskonałym przykładem pokazującym, że (1) przed napisaniem regexu, musisz dokładnie wiedzieć, co próbujesz dopasować, a co nie; oraz (2) często istnieje kompromis pomiędzy tym, co jest dokładne, a tym, co jest praktyczne.
Ceną mojego powyższego wyrażenia regularnego jest to, że pasuje ono do 99% adresów email będących obecnie w użyciu. Wszystkie dopasowane adresy email mogą być obsługiwane przez 99% wszystkich programów pocztowych. Jeśli szukasz szybkiego rozwiązania, wystarczy, że przeczytasz następny akapit. Jeśli chcesz poznać wszystkie kompromisy i otrzymać wiele alternatyw do wyboru, czytaj dalej.
Jeśli chcesz użyć powyższego wyrażenia regularnego, musisz zrozumieć dwie rzeczy. Po pierwsze, długie regexy utrudniają ładne formatowanie akapitów. Dlatego nie uwzględniłem a-z w żadnej z trzech klas znaków. Ten regex jest przeznaczony do użycia z włączoną opcją „case insensitive” w Twoim silniku regex. Po drugie, powyższy regex jest ograniczony granicami słów, co czyni go odpowiednim do wyodrębniania adresów email z plików lub większych bloków tekstu. Jeśli chcesz sprawdzić, czy użytkownik wpisał poprawny adres e-mail, zastąp granice słów kotwicami początku i końca łańcucha, jak poniżej: ^+@+$.
Poprzedni akapit dotyczy również wszystkich poniższych przykładów. Być może będziesz musiał zmienić granice słów na kotwice początku/końca łańcucha, lub odwrotnie. I musisz włączyć opcję dopasowywania bez rozróżniania wielkości liter.
Zmiany w sprawdzaniu adresów e-mail
Zanim ICANN umożliwił każdej dobrze finansowanej firmie tworzenie własnych domen najwyższego poziomu, najdłuższymi domenami najwyższego poziomu były rzadko używane .museum i .travel, które mają 6 liter. Najczęstsze domeny najwyższego poziomu miały 2 litery długości dla domen specyficznych dla danego kraju i 3 lub 4 litery długości dla domen ogólnego przeznaczenia, takich jak .com i .info. Wiele regexów do sprawdzania poprawności adresów e-mail, które można znaleźć w różnych tutorialach i referencjach dotyczących regexów, nadal zakłada, że domena najwyższego poziomu jest dość krótka. Starsze wydania tego samouczka regex wspominały we wstępie o \b+@@.{2,4}\b jako regex dla adresów email. Jest tylko jedna mała różnica pomiędzy tym regexem a tym na górze tej strony. 4 na końcu regex ogranicza domenę najwyższego poziomu do 4 znaków. Jeśli użyjesz tego regex z kotwicami do walidacji adresu e-mail wpisanego w formularzu zamówienia, [email protected] będzie musiał zrobić zakupy gdzie indziej. Tak, .solutions TLD istnieje i kiedy to piszę, disaproved.solutions może być Twoja za $16.88 rocznie.
Jeśli chcesz być bardziej rygorystyczny niż {2,} dla domeny najwyższego poziomu, ^+@+}$ jest tak daleko, jak to praktycznie możliwe. Każda część nazwy domeny nie może być dłuższa niż 63 znaki. Nie istnieją jednocyfrowe domeny najwyższego poziomu i żadna nie zawiera cyfr. Nie wygląda na to, aby ICANN zatwierdził takie domeny.
Adresy e-mail mogą znajdować się na serwerach w subdomenie, jak w [email protected]. Wszystkie powyższe regexy pasują do tego adresu e-mail, ponieważ umieściłem kropkę w klasie znaków po symbolu @. Ale powyższe regexy pasują również do adresu john@aol…com, który nie jest poprawny z powodu następujących po sobie kropek. Możesz wykluczyć takie dopasowania poprzez zastąpienie (?:+) przez (?:+))+ w każdym z powyższych regexów. Usunąłem kropkę z klasy znaków i zamiast tego powtórzyłem klasę znaków i następującą po niej dosłowną kropkę. Np. ^+@(?:+\.)+{2,}$ pasuje do [email protected], ale nie do john@aol…com.
Jeśli chcesz uniknąć dławienia się systemu na arbitralnie dużych danych wejściowych, możesz zastąpić nieskończone kwantyfikatory skończonymi. ^{1,64}@(?:{1,63}){1,125}{2,63}$ bierze pod uwagę, że część lokalna (przed @) jest ograniczona do 64 znaków i że każda część nazwy domeny jest ograniczona do 63 znaków. Nie ma bezpośredniego ograniczenia na liczbę subdomen. Ale maksymalna długość adresu e-mail, który może być obsługiwany przez SMTP wynosi 254 znaki. Tak więc przy jednoznakowej części lokalnej, dwuliterowej domenie najwyższego poziomu i jednoznakowych subdomenach, 125 jest maksymalną liczbą subdomen.
Poprzedni regex nie ogranicza w rzeczywistości adresów e-mail do 254 znaków. Jeśli każda część ma swoją maksymalną długość, to regex może dopasować łańcuchy o długości do 8129 znaków. Można to ograniczyć zmniejszając liczbę dozwolonych subdomen ze 125 do czegoś bardziej realistycznego, jak 8. Nigdy nie widziałem adresu email z więcej niż 4 subdomenami. Jeśli chcesz wymusić przestrzeganie limitu 254 znaków, najlepszym rozwiązaniem jest sprawdzenie długości łańcucha wejściowego przed użyciem regexa. Chociaż wymaga to kilku linii kodu proceduralnego, sprawdzanie długości łańcucha jest niemal natychmiastowe. Jeśli możesz używać tylko regexów, ^{6,254}$ może być użyte jako pierwsze przejście, aby upewnić się, że łańcuch nie zawiera nieprawidłowych znaków i nie jest zbyt krótki lub zbyt długi. Jeśli chcesz zrobić wszystko za pomocą jednego regexa, będziesz potrzebował regexa obsługującego funkcję lookahead. Wyrażenie regularne ^(?={6,254}$){1,64}@(?:{1,63}}) {1,8}{2,63}$ używa lookahead do sprawdzenia, czy łańcuch nie zawiera nieprawidłowych znaków i czy nie jest za krótki lub za długi. Gdy lookahead się powiedzie, reszta regexa wykonuje drugie przejście przez łańcuch, aby sprawdzić poprawność umieszczenia znaku @ i kropek.
Wszystkie te regexy pozwalają na użycie znaków ._%+- w dowolnym miejscu części lokalnej. Można wymusić, aby część lokalna zaczynała się od litery, używając ^{0,63} zamiast ^{1,64} dla części lokalnej: ^{0,63}@(?:{1,63}\.){1,125}{2,63}$. Gdy używamy lookahead do sprawdzenia całkowitej długości adresu, pierwszy znak może być sprawdzony w lookahead. Nie musimy powtarzać sprawdzania początkowego znaku podczas sprawdzania długości części lokalnej. Ten regex jest zbyt długi, aby zmieścić się na szerokości strony, więc włączmy tryb wolnych odstępów:
^(?={5,253}$)
{1,64}@(?:{1,63}$){1,8}{2,63}$
Nazwy domen mogą zawierać myślniki. Ale nie mogą zaczynać się ani kończyć myślnikiem. (?:{0,62})? pasuje do nazwy domeny o długości od 1 do 63 znaków, która zaczyna się i kończy literą lub cyfrą. Grupa nie przechwytująca czyni środek domeny i ostatnią literę lub cyfrę opcjonalną jako całość, aby zapewnić, że dopuszczamy domeny jednoznakowe, jednocześnie zapewniając, że domeny z dwoma lub więcej znakami nie kończą się myślnikiem. Ogólny regex zaczyna się robić dość skomplikowany:
^{0,63}@
(?:(?:{0,62})?\)){1,8}{2,63}$
Nazwy domen nie mogą zawierać kolejnych myślników. +(?:-+)* pasuje do nazwy domeny, która zaczyna się i kończy literą lub cyfrą i która zawiera dowolną ilość nie następujących po sobie myślników. Jest to najbardziej efektywny sposób. Ten regex nie robi żadnego backtrackingu, aby dopasować poprawną nazwę domeny. Dopasowuje wszystkie litery i cyfry na początku nazwy domeny. Jeśli nie ma myślników, opcjonalna grupa, która następuje po nich, natychmiast zawodzi. Jeśli są myślniki, grupa dopasowuje każdy myślnik, po którym następują wszystkie litery i cyfry aż do następnego myślnika lub końca nazwy domeny. Nie możemy wymusić maksymalnej długości, gdy myślniki muszą być połączone z literą lub cyfrą, ale litery i cyfry mogą stać samodzielnie. Możemy jednak użyć techniki lookahead, której użyliśmy do wymuszenia ogólnej długości adresu e-mail, aby wymusić długość nazwy domeny, jednocześnie nie dopuszczając kolejnych myślników: (?={1,63}\.)+(?:-+)*. Zauważ, że lookahead sprawdza również kropkę, która musi pojawić się po nazwie domeny, gdy jest ona w pełni kwalifikowana w adresie e-mail. Jest to ważne. Bez sprawdzania kropki, lookahead zaakceptowałby dłuższe nazwy domen. Ponieważ lookahead nie konsumuje tekstu, który dopasowuje, kropka nie jest uwzględniana w ogólnym dopasowaniu tego regexa. Gdy umieścimy ten regex w ogólnym regexie dla adresów email, kropka zostanie dopasowana tak samo, jak w poprzednich regexach:
^{0,63}@
(?:(?={1,63}})+(?:-+)*\.){1,8}{2,63}$
Jeśli uwzględnimy lookahead do sprawdzenia długości całkowitej, nasz regex wykona dwa przejścia po części lokalnej i trzy po nazwach domen, aby wszystko sprawdzić:
^(?={5,253}$){1,64}@
(?:(?={1,63}$)+(?:-+)*$){1,8}{2,63}$
Na nowoczesnym komputerze lub serwerze ten regex będzie działał bez zarzutu podczas sprawdzania poprawności 254-znakowego adresu e-mail. Odrzucanie dłuższych danych wejściowych będzie nawet szybsze, ponieważ regex zawiedzie, gdy lookahead zawiedzie podczas pierwszego przejścia. Nie zalecałbym jednak używania tak złożonego regexa do wyszukiwania adresów email w dużym archiwum dokumentów lub korespondencji. Lepiej jest użyć prostego regexa z góry tej strony, aby szybko zebrać wszystko, co wygląda jak adres e-mail. Deduplikuj wyniki, a następnie użyj bardziej rygorystycznego regexa, jeśli chcesz dalej odfiltrować nieważne adresy.
A propos backtrackingu, żaden z regexów na tej stronie nie robi żadnego backtrackingu, aby dopasować ważne adresy email. Jednak szczególnie te ostatnie mogą wykonać sporo backtrackingu na czymś, co nie jest do końca poprawnym adresem e-mail. Jeśli Twój regex obsługuje kwantyfikatory dzierżawcze, możesz wyeliminować wszelkie backtracking poprzez uczynienie wszystkich kwantyfikatorów dzierżawczymi. Ponieważ nie ma potrzeby cofania się do znalezienia dopasowań, nie zmienia to tego, co jest dopasowywane przez te regexy. Pozwala im jedynie szybciej zawodzić, gdy dane wejściowe nie są poprawnym adresem e-mail. Nasz najprostszy regex staje się wtedy ^++@++\\.{2,}+$ z dodatkowym + po każdym kwantyfikatorze. To samo możemy zrobić z naszym najbardziej złożonym regexem:
^(?={5,253}+$){1,64}+@
(?:(?={1,63}+$)++(?:-++)*+$){1,8}+{2,63}+$
Ważnym kompromisem we wszystkich tych regexach jest to, że dopuszczają one tylko angielskie litery, cyfry i najczęściej używane symbole specjalne. Głównym powodem jest to, że nie ufam, że wszystkie moje programy pocztowe będą w stanie obsłużyć wiele innych. Nawet jeśli John.O’[email protected] jest poprawnym składniowo adresem e-mail, istnieje ryzyko, że niektóre programy błędnie zinterpretują apostrof jako cudzysłów. Ślepe wstawienie tego adresu e-mail do zapytania SQL, na przykład, w najlepszym przypadku spowoduje jego awarię, gdy ciągi są ograniczone pojedynczymi cudzysłowami, a w najgorszym otworzy Twoją witrynę na ataki SQL injection.
I oczywiście, już od wielu lat nazwy domen mogą zawierać znaki inne niż angielskie. Ale większość oprogramowania wciąż trzyma się 37 znaków, do których przywykli zachodni programiści. Obsługa domen międzynarodowych otwiera całą puszkę robaków, w jaki sposób powinny być kodowane znaki spoza ASCII. Jeśli więc użyjesz któregoś z regexów na tej stronie, każdy kto ma adres @ทีเอชนิค.ไทย będzie miał pecha. Ale być może wymowne jest to, że http://ทีเอชนิค.ไทย po prostu przekierowuje do http://thnic.co.th nawet jeśli zajmują się sprzedażą domen .ไทย.
Wniosek jest taki, że aby zdecydować, którego wyrażenia regularnego użyć, niezależnie od tego, czy próbujesz dopasować adres e-mail, czy coś innego, co jest niejasno zdefiniowane, musisz zacząć od rozważenia wszystkich kompromisów. Jak źle jest dopasować coś, co nie jest ważne? Jak źle jest nie dopasować czegoś, co jest ważne? Jak skomplikowane może być twoje wyrażenie regularne? Jak kosztowne byłoby, gdybyś musiał później zmienić wyrażenie regularne, ponieważ okazało się ono zbyt szerokie lub zbyt wąskie? Różne odpowiedzi na te pytania będą wymagały innego wyrażenia regularnego jako rozwiązania. Mój regex emailowy robi to, czego ja chcę, ale może nie robić tego, czego ty chcesz.
Regexes Don’t Send Email
Nie przesadzaj z próbami eliminacji nieprawidłowych adresów email za pomocą wyrażeń regularnych. Powodem tego jest fakt, że tak naprawdę nie wiesz, czy dany adres jest poprawny, dopóki nie spróbujesz wysłać na niego wiadomości e-mail. I nawet to może nie wystarczyć. Nawet jeśli email dotrze do skrzynki pocztowej, nie oznacza to, że ktoś ją czyta. Jeśli naprawdę chcesz mieć pewność, że dany adres e-mail jest ważny, musisz wysłać na niego wiadomość zawierającą kod lub link, dzięki któremu odbiorca będzie mógł wykonać drugi krok uwierzytelniania. A jeśli to robisz, to nie ma sensu używać regexa, który może odrzucać prawidłowe adresy email.
Ta sama zasada ma zastosowanie w wielu sytuacjach. Kiedy próbujesz dopasować poprawną datę, często łatwiej jest użyć odrobiny arytmetyki, aby sprawdzić lata przestępne, niż próbować zrobić to za pomocą regex. Użyj wyrażenia regularnego do znalezienia potencjalnych dopasowań lub sprawdź, czy dane wejściowe używają właściwej składni, a następnie wykonaj rzeczywistą walidację na potencjalnych dopasowaniach zwróconych przez wyrażenie regularne. Wyrażenia regularne są potężnym narzędziem, ale daleko im do panaceum.
Oficjalny standard: RFC 5322
Może zastanawiasz się, dlaczego nie ma „oficjalnego”, niezawodnego regexu do dopasowywania adresów email. Cóż, istnieje oficjalna definicja, ale raczej nie jest ona niezawodna.
Oficjalny standard znany jest jako RFC 5322. Opisuje on składnię, do której muszą stosować się prawidłowe adresy e-mail. Możesz (ale nie powinieneś – czytaj dalej) zaimplementować go za pomocą następującego wyrażenia regularnego. RFC 5322 pozostawia część dotyczącą nazwy domeny otwartą na wybory specyficzne dla implementacji, które nie będą działać w dzisiejszym Internecie. Wyrażenie regex implementuje „preferowaną” składnię z RFC 1035, która jest jednym z zaleceń w RFC 5322:
A(?:+(?:\\.+)*
| „(?:
| \)*”)
@ (?:(?(?:(?:*)?\)?+(?:*)?\)?{3}
(?:25|2|??|*:
(?:
| \)+)
])\z
Ten regex ma dwie części: część przed @, i część po @. Istnieją dwie alternatywy dla części przed @. Pierwsza alternatywa pozwala, aby składała się ona z serii liter, cyfr i niektórych symboli, w tym jednej lub więcej kropek. Kropki nie mogą jednak występować kolejno ani na początku lub końcu adresu e-mail. Druga alternatywa wymaga, aby część przed @ była ujęta w cudzysłów, dopuszczając dowolny ciąg znaków ASCII pomiędzy cudzysłowami. Znaki białych spacji, podwójne cudzysłowy i odwrotne ukośniki muszą być usunięte za pomocą odwrotnych ukośników.
Część po @ również ma dwie alternatywy. Może to być albo w pełni kwalifikowana nazwa domeny (np. regular-expressions.info), albo dosłowny adres internetowy w nawiasach kwadratowych. Dosłowny adres internetowy może być albo adresem IP, albo adresem routingu specyficznym dla danej domeny.
Powodem, dla którego nie powinieneś używać tego regexa jest to, że jest on zbyt szeroki. Twoja aplikacja może nie być w stanie obsłużyć wszystkich adresów e-mail, na które pozwala ten regex. Adresy routingu specyficzne dla domeny mogą zawierać niedrukowalne znaki sterujące ASCII, co może powodować problemy, jeśli Twoja aplikacja musi wyświetlać adresy. Nie wszystkie aplikacje obsługują składnię części lokalnej z użyciem podwójnych cudzysłowów lub nawiasów kwadratowych. W rzeczywistości sam RFC 5322 oznacza notację z użyciem nawiasów kwadratowych jako przestarzałą.
Uzyskamy bardziej praktyczną implementację RFC 5322, jeśli pominiemy adresy IP, adresy specyficzne dla domeny, składnię z użyciem podwójnych cudzysłowów i nawiasów kwadratowych. Nadal będzie ona pasować do 99,99% wszystkich adresów e-mail będących obecnie w użyciu.
(?:(?:*)?\)*@
(?:(?:*)?\.)+(?:*)?\z
Żaden z tych regexów nie wymusza ograniczeń długości całego adresu e-mail ani części lokalnej czy nazw domen. RFC 5322 nie określa żadnych ograniczeń długości. Wynikają one z ograniczeń w innych protokołach, takich jak protokół SMTP do faktycznego wysyłania wiadomości e-mail. RFC 1035 stwierdza, że domeny muszą mieć długość 63 znaków lub mniejszą, ale nie uwzględnia tego w specyfikacji składni. Powodem jest to, że prawdziwy język regularny nie może jednocześnie wymuszać limitu długości i nie zezwalać na kolejne myślniki. Jednak współczesne regex flavors nie są prawdziwie regularne, więc możemy dodać sprawdzanie limitu długości za pomocą lookahead, tak jak robiliśmy to wcześniej:
A(?={6,254}}z)
(?={1,64}@)
+(?)*
@ (?:(?={1,63}})(?:*))+
(?={1,63}})(?:*)?\z
Więc nawet podążając za oficjalnymi standardami, wciąż istnieją kompromisy, których należy dokonać. Nie należy ślepo kopiować wyrażeń regularnych z bibliotek internetowych lub forów dyskusyjnych. Zawsze testuj je na własnych danych i z własnymi aplikacjami.
Wpuść darowiznę
Czy ta strona właśnie zaoszczędziła Ci wycieczki do księgarni? Przekaż darowiznę na wsparcie tej strony, a otrzymasz dożywotni wolny od reklam dostęp do tej strony!