メールアドレスの正規表現の「$」が、最後の改行を通した ― その書き方、本当に通る?⑥

C#
B!

問い合わせフォームのメールアドレスの欄を、正規表現で確かめる。よく見かける書き方です。

^[\w.+-]+@[\w-]+\.[\w.-]+$

先頭の ^ と最後の $ で「文字列全体がこの形か」を確かめた、つもりの正規表現です。これに、最後に改行が付いた "taro@example.com\n" を渡すと、正規表現を使った9言語中7言語が「OK」と答えました。

何をしたか

CODE Polyglot の入門 第16回 は、受け取った文字列がメールアドレスの形かを確かめるプログラムです。前後の空白や改行も含めて、文字列全体がこの形でなければ NG が正解です。

各言語の普通の一致の関数に、上の正規表現を渡しました(Rust には標準の正規表現が無いので、「@ と . が入っているか」で確かめる形にしました)。毎朝の検証と同じ Linux の環境で動かした結果です。

言語(一致の関数)最後に改行1行目が hello、2行目にアドレス
Python re.matchOK と答えたNG
PHP preg_matchOK と答えたNG
Java find・Kotlin containsMatchInOK と答えたNG
C# Regex.IsMatchOK と答えたNG
Swift range(of:options: .regularExpression)OK と答えたNG
Ruby =~OK と答えたOK と答えた
Go regexp.MatchString・JavaScript testNGNG

「$」は、最後の改行の直前にも合う

多くの正規表現で、$ は「文字列の最後」だけでなく「最後の改行の直前」にも合います。taro@example.com までで形が合い、残りの \n は $ が見逃します。Go と JavaScript の $ は文字列の最後にだけ合うので、通しませんでした。

Ruby はもう一段ゆるく、^ と $ が「行の先頭」「行の最後」を意味します。"hello\ntaro@example.com" は、2行目だけで形が合うので「OK」になりました。10言語中、2行目のアドレスを通したのは Ruby だけです(Rust は「@ と . が入っているか」だけを見たので、改行のケースも2行目のケースも、正規表現とは別の理由で通しています)。

Swift は、Mac と Linux で答えが違った

Swift の range(of:options: .regularExpression) は、毎朝の検証の Linux では改行のケースを通しました。同じ関数が Mac ではどうなるかを、短い正規表現で比べました(どちらも Swift 6.3.3)。

正規表現入力Mac range(of:)Mac NSRegularExpressionLinux(どちらも)
^[a-z@.]+$"taro@example.com\n"合わない合う合う
^[a-z@.]+\z"taro@example.com\n"合わない合わない合わない

Mac でも NSRegularExpression では合ったので、「$ が最後の改行の直前にも合う」こと自体は同じです。Mac の range(of:options:) だけが、$ を文字列の最後にしか合わせませんでした(Mac の中で何をしているかまでは確かめていません)。手元の Mac で通っても、Linux のサーバーでは改行を通すことがあります。\z を使えば、どちらでも同じ答えになりました。

どう直したか

「文字列全体に合うか」を調べる関数を使うか、「文字列の最初」「文字列の最後」だけを意味する \A と \z を使います。

言語書き方
Pythonre.fullmatch(...)
Java・Kotlinmatches
SwiftwholeMatch(of:)
PHP・Ruby・C#\A…\z で挟む

Ruby ならこうです(LOCAL と LABEL は、ローカル部とドメインの1区切りの正規表現)。

# Ruby の「^」「$」は行の先頭・行の最後なので、文字列の先頭「\A」と最後「\z」を使う
EMAIL = /\A#{LOCAL}@(#{LABEL}\.)+[A-Za-z]{2,}\z/

あわせて \w もやめ、使ってよい文字を [A-Za-z0-9_+-] のように書きました。\w は、Python・C#・Swift ではひらがなにも合い、たろう@example.com を通したからです。直した版は、10言語とも11件中11件が正解になりました。

まとめ

  • ^…$ は「文字列全体」の意味にならないことがある。最後の改行を、9言語中7言語が通した
  • Ruby の ^ $ は行単位。2行目だけが正しい形でも通る
  • Swift の range(of:options:) は、Mac と Linux で違う答えを出した。本番と同じ環境で確かめる
  • 全体を確かめるなら fullmatch・matches・wholeMatch、または \A…\z
  • 10言語の全プログラムは 入門 第16回 にあります
連載「その書き方、本当に通る?」(全8回)
  1. 第1回:「2026-02-30」を渡したら、PHP と JavaScript だけが黙って3月2日にした
  2. 第2回:JavaScript で「月の最終日」を出したら、日本では毎回1日前になった
  3. 第3回:C# の Normalize(FormKC) が、Docker の中では何もしなかった
  4. 第4回:辞書の「取り出す順番」を10言語で20回ずつ試したら、5通りの性格に分かれた
  5. 第5回:「+5」を整数に直したら、10言語すべてが黙って 5 にした
  6. 第6回:メールアドレスの正規表現の「$」が、最後の改行を通した(この記事)
  7. 第7回:「東京,大阪,」を split したら、Java・Ruby・Swift は最後の空の項目を捨てた
  8. 第8回:Excel の「CSV UTF-8」を読ませたら、10言語すべてが見出しの行を見失った
B!
← 一覧へ戻る