問い合わせフォームのメールアドレスの欄を、正規表現で確かめる。よく見かける書き方です。
^[\w.+-]+@[\w-]+\.[\w.-]+$
先頭の ^ と最後の $ で「文字列全体がこの形か」を確かめた、つもりの正規表現です。これに、最後に改行が付いた "taro@example.com\n" を渡すと、正規表現を使った9言語中7言語が「OK」と答えました。
何をしたか
CODE Polyglot の入門 第16回 は、受け取った文字列がメールアドレスの形かを確かめるプログラムです。前後の空白や改行も含めて、文字列全体がこの形でなければ NG が正解です。
各言語の普通の一致の関数に、上の正規表現を渡しました(Rust には標準の正規表現が無いので、「@ と . が入っているか」で確かめる形にしました)。毎朝の検証と同じ Linux の環境で動かした結果です。
| 言語(一致の関数) | 最後に改行 | 1行目が hello、2行目にアドレス |
|---|---|---|
Python re.match | OK と答えた | NG |
PHP preg_match | OK と答えた | NG |
Java find・Kotlin containsMatchIn | OK と答えた | NG |
C# Regex.IsMatch | OK と答えた | NG |
Swift range(of:options: .regularExpression) | OK と答えた | NG |
Ruby =~ | OK と答えた | OK と答えた |
Go regexp.MatchString・JavaScript test | NG | NG |
「$」は、最後の改行の直前にも合う
多くの正規表現で、$ は「文字列の最後」だけでなく「最後の改行の直前」にも合います。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 NSRegularExpression | Linux(どちらも) |
|---|---|---|---|---|
^[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 を使います。
| 言語 | 書き方 |
|---|---|
| Python | re.fullmatch(...) |
| Java・Kotlin | matches |
| Swift | wholeMatch(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回 にあります
- 第1回:「2026-02-30」を渡したら、PHP と JavaScript だけが黙って3月2日にした
- 第2回:JavaScript で「月の最終日」を出したら、日本では毎回1日前になった
- 第3回:C# の Normalize(FormKC) が、Docker の中では何もしなかった
- 第4回:辞書の「取り出す順番」を10言語で20回ずつ試したら、5通りの性格に分かれた
- 第5回:「+5」を整数に直したら、10言語すべてが黙って 5 にした
- 第6回:メールアドレスの正規表現の「$」が、最後の改行を通した(この記事)
- 第7回:「東京,大阪,」を split したら、Java・Ruby・Swift は最後の空の項目を捨てた
- 第8回:Excel の「CSV UTF-8」を読ませたら、10言語すべてが見出しの行を見失った
Analyzegear


