CODE Polyglot / 入門 / 第16回

メールアドレスの形を確かめる — 正規表現の ^ と $ と \w

受け取った文字列が、メールアドレスの形かを確かめます。問い合わせフォームの入力チェックです。正規表現の「^」「$」「\w」の意味が言語によって少しずつ違い、改行や日本語を通してしまうことがあります。

画面にこう出れば正解

どの言語で書いても、同じ入力なら画面に出る文字は1文字も違わない、というのが正解の決め方です(例は Python で動かしたときのコマンドです)。

基本

$ python3 email.py taro@example.com
OK: taro@example.com

記号と2段のドメイン

$ python3 email.py taro.yamada+news@example.co.jp
OK: taro.yamada+news@example.co.jp

ドメインに点がない

$ python3 email.py taro@example
NG: メールアドレスの形ではありません

@が2つ

$ python3 email.py taro@@example.com
NG: メールアドレスの形ではありません

@の前が空

$ python3 email.py @example.com
NG: メールアドレスの形ではありません

日本語

$ python3 email.py たろう@example.com
NG: メールアドレスの形ではありません

最後に改行

$ python3 email.py "taro@example.com
"
NG: メールアドレスの形ではありません

2行目にアドレス

$ python3 email.py "hello
taro@example.com"
NG: メールアドレスの形ではありません

前に空白

$ python3 email.py " taro@example.com"
NG: メールアドレスの形ではありません

点が続く

$ python3 email.py taro..yamada@example.com
NG: メールアドレスの形ではありません

全角の@

$ python3 email.py taro@example.com
NG: メールアドレスの形ではありません

10言語のプログラム

どれもそのまま保存して動かせます。毎朝、上の正解の例をすべて実際に動かして確かめています。

email.php8.4.26 で 11 / 11 件の出力が正解と一致

<?php
// 受け取った文字列が、メールアドレスの形かを確かめる
$s = $argv[1];

// ローカル部は「.」で区切った、英数字と _ + - の並び(先頭・最後の「.」と「..」を通さない)。
// ドメインは、英数字で始まり英数字で終わる部分を「.」でつなぎ、最後は英字2文字以上
$local = '[A-Za-z0-9_+-]+(\.[A-Za-z0-9_+-]+)*';
$label = '[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?';
// 「$」は最後の改行の直前にも合うので、文字列の本当の最後を表す「\z」を使う
if (preg_match('/\A' . $local . '@(' . $label . '\.)+[A-Za-z]{2,}\z/', $s)) {
    echo "OK: " . $s . "\n";
} else {
    echo "NG: メールアドレスの形ではありません\n";
}

動かし方(ファイルを保存したフォルダで) ・ PHP を入れる

$ php email.php taro@example.com

言語でつまずきやすいところ

初心者がよく書く形(各言語で最初に習う書き方をそのまま使ったもの)を、同じ正解表で動かした結果です。言語を出した答えごとにまとめ、赤い字はその言語が実際に出した間違った行です。

入力初心者がよく書く形の答え(10言語)
基本taro@example.com

✓10言語すべて正解

記号と2段のドメインtaro.yamada+news@example.co.jp

✓10言語すべて正解

ドメインに点がないtaro@example

✓10言語すべて正解

@が2つtaro@@example.com
  • RustOK: taro@@example.com
  • PHP・Python・Go・JavaScript・Java・C#・Ruby・Kotlin・Swift✓ 正解
@の前が空@example.com
  • RustOK: @example.com
  • PHP・Python・Go・JavaScript・Java・C#・Ruby・Kotlin・Swift✓ 正解
日本語たろう@example.com
  • Python・C#・Rust・SwiftOK: たろう@example.com
  • PHP・Go・JavaScript・Java・Ruby・Kotlin✓ 正解
最後に改行"taro@example.com "
  • PHP・Python・Java・C#・Rust・Ruby・Kotlin・SwiftOK: taro@example.com
  • Go・JavaScript✓ 正解
2行目にアドレス"hello taro@example.com"
  • Rust・RubyOK: hello
    taro@example.com
  • PHP・Python・Go・JavaScript・Java・C#・Kotlin・Swift✓ 正解
前に空白" taro@example.com"
  • RustOK: taro@example.com
  • PHP・Python・Go・JavaScript・Java・C#・Ruby・Kotlin・Swift✓ 正解
点が続くtaro..yamada@example.com
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・SwiftOK: taro..yamada@example.com
全角の@taro@example.com

✓10言語すべて正解

この回でやること

  • 正規表現で、文字列全体の形を確かめる
  • 改行や空白が混ざった入力を、通さない

この回の決まり

この回では、よく使われる形に絞った、次の決まりにします(本物のメールアドレスの決まりは、もっと複雑です)。

  • 「@」がちょうど1つ。前(ローカル部)と後ろ(ドメイン)に分ける
  • ローカル部: 半角の英数字と「. _ + -」だけ。先頭と最後は「.」でない。「..」と続かない
  • ドメイン: 「.」で区切った2つ以上の部分。各部分は半角の英数字と「-」だけで、先頭と最後は「-」でない。最後の部分は英字2文字以上
  • 前後の空白や改行も含めて、文字列全体がこの形でなければ NG

解説

「文字列全体」を確かめる

正規表現は、何も指定しなければ「どこかに合う部分があるか」を探します。文字列全体を確かめるには、先頭の「^」と最後の「$」を付けるか、全体に合うかを調べる関数を使います。

「$」は、最後の改行の前にも合うことがある

初心者がよく書く正規表現 ^[\w.+-]+@[\w-]+\.[\w.-]+$ を、各言語の普通の一致の関数で試しました。

入力通してしまった言語
taro@example.com の最後に改行PHP・Python・Ruby・Java・Kotlin・C#・Swift(Linux)。「$」が最後の改行の直前にも合う
1行目が hello、2行目に taro@example.comRuby。「^」と「$」が行の先頭・行の最後を意味する
たろう@example.comPython・C#・Swift。「\w」がひらがなにも合う
taro..yamada@example.com正規表現を使った9言語すべて。[\w.+-]+ は点が続いても合う(正規表現そのものの穴)

Go と JavaScript の「$」は文字列の最後にだけ合うので、改行のケースは通しませんでした。Swift の range(of:options: .regularExpression) は、Mac では改行のケースを通さず、Linux では通しました。同じプログラムでも、動かす場所で結果が変わります。Mac でも NSRegularExpression で同じ正規表現を試すと改行のケースに合ったので、違いは正規表現そのものではなく、Mac の range(of:options:) の動きです。\z を使えば、Mac でも Linux でも同じ結果になりました。

Rust には標準の正規表現がありません。「@ と . が入っているか」だけで確かめると、11件中4件しか合いませんでした。

文字列全体を確かめるには、全体に合うかを調べる関数(Python re.fullmatch・Java matches・Kotlin matches・Swift wholeMatch)を使うか、「文字列の最初と最後」だけを意味する \A と \z を使います。

「\w」は、日本語にも合うことがある

「\w」(英数字と _)は、言語によっては、ひらがなや漢字にも合います(上の表では Python・C#・Swift)。第7回 の「\d」と同じ問題です。この回では、使ってよい文字を [A-Za-z0-9._+-] のように書いて決めています。

練習

  1. ドメインを「example.com」だけに限ってみましょう
  2. NG のとき、どこが違うのか(@ がない、ドメインに点がない、など)を出し分けてみましょう