初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」

  • 1410
    いいね
  • 7
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

Qiitaをご覧になっているエンジニアのみなさん、正規表現は使いこなせてますか?
正規表現が使えるととっても便利ですよね!

あれ?そちらの方、「ぼく、正規表現ようわからへん・・・」って小さくなってませんか??

大丈夫です!そんなあなたのために、この記事を書きました。
知識ゼロからでも正規表現を学べるようにやさしく説明しているので、とりあえずこの記事を最後まで読んでみてください。

今は \d{2,5}[-(]\d{1,4}[-)]\d{4} が謎の呪文にしか見えなくても、最後まで読めばきっと意味がわかるようになっているはずです!

対象となる読者

本記事は正規表現の予備知識が全くない「正規表現初心者」を対象としています。

  • 正規表現は便利だってよく聞くけど、意味不明な呪文にしか見えなくてなんか怖い
  • 正規表現を勉強しようと何度か頑張ったけど、結局よくわからなくて実務で活用できていない

といった人たちにもわかりやすく教えるつもりです。

また、記事の本文では正規表現の動きを視覚的に確認できる便利サイトを使っています。
このサイトを使うと正規表現の仕組みがよくわかるので、実際に自分の手で動かすことを強くオススメします!

この記事で学ぶこと

本記事では正規表現を利用した電話番号の検索を通じて、正規表現に関する以下のような知識を学びます。

  • \d{n,m}{n}[AB][a-z]の意味
  • 正規表現の正確さと複雑さの関係

さらに、例題を実際に自分で動かしながら正規表現の概要を理解し、 正規表現に対する苦手意識を克服してもらうこと を目標としています。

なお、「手と目で覚える正規表現入門」はいくつかの回に分けて正規表現を説明する予定です。
この1回目の記事ではごく初歩的なことしか説明しないため、「これさえ読めば今日からバリバリ正規表現が使える!」というわけにはいかない点を予めご了承ください。

2016.02.23追記:すべての記事を公開しました!
すべての記事を公開しました。全4回です。
どんどん実践的な内容になっていくので、こちらも続けて読んでみてください。

動作環境

本記事の正規表現は以下の環境で動作確認しています。

  • Ruby
  • JavaScript
  • Atom

とはいえ、正規表現はそれ自体が独立したミニ言語みたいなものなので、プログラミング言語やテキストエディタを問わず、同じように使えます。
ただし、微妙に使える機能や挙動が異なったり、実行環境に合わせたエスケープ文字を入れる必要があったりするので、その点は注意してください。

この記事の方針

この記事はあくまで入門記事なので、100%正しい複雑な正規表現よりも、そこそこ正確でわかりやすい正規表現を重視します。

今回は電話番号を検索する正規表現を例題として使っていますが、正規表現の熟練者が見ると「いや、その正規表現は不完全だ!」「自分ならこう書く!」と感じるところがあるかもしれません。
ただ、本記事ではあくまで正規表現の基本を教えるのが第一の目的であり、「電話番号の完璧な正規表現」を教えることが目的ではないので、その点をご理解ください。

それでは以下が本編です!

正規表現とは

Wikipediaで正規表現を調べると次のように書いてあります。

正規表現(せいきひょうげん、英: regular expression)とは、文字列の集合を一つの文字列で表現する方法の一つである。

正規表現 - Wikipedia

うーむ、これだけだとなんかよくわかりませんね・・・。

Wikipediaの説明を無視して、極めて個人的な視点で正規表現を説明するなら、

「パターンを指定して、文字列を効率よく検索・置換するためのミニ言語」

かなーと思っています。

まあいずれにせよ、初心者の方はピンと来ないでしょう。
大丈夫です。
今の段階ではそこまで深く考えずに本記事を読み進めていけば、最後には正規表現が何なのか、どこがどう便利なのかわかってくるはずです。

なお、Wikipediaにもあるとおり、正規表現は英語で Regular expression といいます。
プログラミング言語やエディタ上では略して、Regex や Regexp と書かれることが多いので、英語の略称も頭の片隅に置いておきましょう。

電話番号を探す

まず最初に、正規表現で電話番号を検索してみましょう。
最初に用意するのはこんなテキストです。

名前:伊藤淳一
電話:03-1234-5678
住所:兵庫県西脇市板波町1-2-3

本記事では正規表現をわかりやすく学習するために、今回はRubularというWebサイトを利用します。
http://rubular.com/ にアクセスしたら上のテキストを「Your test string」欄にコピペしてください。

Screen Shot 2016-02-01 at 4.36.07.png
※スマートフォンで見ると画像が小さいかもしれないので、PCで見ることをオススメします。

次に「Your regular expression」欄に \d と入力してください。
すると、「Match result」欄に水色背景の文字が表示されます。

Screen Shot 2016-02-01 at 4.38.08.png

水色になっているのが、正規表現にマッチした部分、すなわち検索して見つかった部分です。
ここでは半角数字が全部水色背景になっています。

先ほど入力してもらった \d は正規表現では特別な意味を持ちます。
こういった特別な文字を 「メタ文字」 と呼びます。
\d は「1個の半角数字(0123456789)」を意味するメタ文字です(文字の集合を表しているので、特に 「文字クラス」 と呼ばれます)。
普通の検索であれば \d は "\d" という文字列そのものを検索しますが、正規表現では \d は「1個の半角数字」を検索します。

今後、正規表現中に \d が出てきたら、「あ、これは半角数字を意味してるんだな」と頭の中で意味を変換してください。

メタ文字をつなげて文字列を検索してみる

さて、今のままだと電話番号だけをきれいに検索できていません。
そこで次のような正規表現を入力してください。

\d\d-\d\d\d\d-\d\d\d\d

Screen Shot 2016-02-01 at 4.46.01.png

こうすると電話番号だけをきれいに検索することができました。
\d は「1個の半角数字」なので、\d\d は半角数字2個の意味になります。
- はメタ文字ではないので "-" という文字そのものを表します。(ただし、使い方によっては - も特殊な意味を持ちます。本記事の後半で説明します)

よって、 \d\d-\d\d\d\d-\d\d\d\d は「半角数字2個、ハイフン、半角数字4個、ハイフン、半角数字4個」が並んだ文字列を検索したことになります。

試しに、電話番号を "06-9999-9999" に変えてみましょう。
「半角数字2個、ハイフン、半角数字4個、ハイフン、半角数字4個」というパターンに合致するので、これも検索できているはずです。

Screen Shot 2016-02-01 at 4.51.42.png

Rubyのコード上で動かしてみる

正規表現はRubularの中だけでなく、プログラミング言語やテキストエディタ内でも使用できます。

以下はRubyで正規表現を使う例です。
/ / で囲まれた部分が正規表現(Regexpクラスのインスタンス)になります。

text = <<-TEXT
名前:伊藤淳一
電話:03-1234-5678
住所:兵庫県西脇市板波町1-2-3
TEXT
text.scan /\d\d-\d\d\d\d-\d\d\d\d/
# => ["03-1234-5678"]

JavaScript上で動かしてみる

Rubyに限らず、モダンで有名なプログラミング言語であれば、ほとんどの言語で正規表現が使えるはずです。
JavaScript(JS)でも正規表現は使えるので、JSを普段よく使う人ならJS上で動作確認するのも良いでしょう。

RubularはRuby内で動作する正規表現をシミュレートするサイトですが、JS向けにもScriptularというほぼ同じサイトが存在します。
JS上の正規表現の動作を確認したい場合はこちらを使うと良いと思います。

http://scriptular.com/
Screen Shot 2016-02-01 at 8.32.26.png

もちろんコード内で正規表現を使うこともできます。
以下は先ほどのRubyのコードと同じ処理をJavaScriptで書いたものです。

var text = "名前:伊藤淳一\n電話:03-1234-5678\n住所:兵庫県西脇市板波町1-2-3";
text.match(/\d\d-\d\d\d\d-\d\d\d\d/g);
// => ["03-1234-5678"]

なお、/\d\d-\d\d\d\d-\d\d\d\d/g の最後の g はグローバルオプションと呼ばれるもので、以下のような違いがあります。

  • g なし => 最初の1件が見つかったら検索終了
  • g あり => 一致する文字列をすべて抽出

テキストエディタ(Atom)上で動かしてみる

正規表現はプログラミング言語だけでなく、テキストエディタでも使えます。
プログラミング向けのテキストエディタであれば、大半のものは正規表現を使って検索や置換ができるはずです。

ここでは参考にAtomでの実行結果を載せておきます。

Screen Shot 2016-02-01 at 8.41.46.png

ご覧の通り、Atomでも正規表現を使って電話番号を検索することができました。
ちなみに正規表現を使う場合は画面右下の「.*」をオンにする必要があります。

このように、正規表現を覚えると言語やエディタを問わず、いろんな場面で活用できるようになります。

いろんな市外局番に対応させる

ところで、電話番号は常に「半角数字2個、ハイフン、半角数字4個、ハイフン、半角数字4個」とは限りません。
携帯番号であれば "090-1234-5678" のようになりますし、僕の住んでいる兵庫県西脇市は "0795-12-3456" のように市外局番が4桁です。
また、地域によっては "04992-1-2345" のように5桁の電話番号もあるようです。

そこで、正規表現を変更してどの市外局番にも対応できるようにしてみましょう。
次のテキストをRubularに貼り付けてください。

名前:伊藤淳一
電話:03-1234-5678
電話:090-1234-5678
電話:0795-12-3456
電話:04992-1-2345
住所:兵庫県西脇市板波町1-2-3

ご覧の通り、先ほどの正規表現だと、新しく追加された電話番号を正しく検索できていません。

Screen Shot 2016-02-01 at 4.57.56.png

これら4つの電話番号にはどんな法則があるでしょうか?
正規表現を使うときは、最初に検索対象文字列の 「法則(パターン)」 をうまく見つけだすのが大切です。

今回であれば、次のような法則があると言えそうです。

「半角数字が2個~5個、ハイフン、半角数字が1個~4個、ハイフン、半角数字が4個」

あとはこれを正規表現として表すことができればOKです。
文字の個数を限定するときは {n,m}{n} というメタ文字を使います(文字量を指定するので、特に 「量指定子」 と呼ばれます)。
{n,m} は「直前の文字が n 個以上、m 個以下」の意味です。
また、 {n} とすれば「ちょうど n 文字」の意味になります。

よって正規表現は以下のようになります。

\d{2,5}-\d{1,4}-\d{4}

Rubularにこの正規表現を入力すると、正しく電話番号を検索することができました!

Screen Shot 2016-02-01 at 5.04.53.png

正規表現では {} もやはり特別な意味を持つことを覚えておいてください。

ハイフンだけでなく、カッコにも対応する

さて、もう少しだけ電話番号の正規表現を考えてみたいと思います。
電話番号は次のように表記されることもあります。
ハイフンではなくカッコを使うケースです。

"03(1234)5678"

ハイフンが来ても、カッコが来ても、どちらでも電話番号を検索できる正規表現はあるのでしょうか?
大丈夫です。そういう場合も正規表現で検索できます!

まずはRubularに次のようなテキストを貼り付けてください。

名前:伊藤淳一
電話:03(1234)5678
電話:090-1234-5678
電話:0795(12)3456
電話:04992-1-2345
住所:兵庫県西脇市板波町1-2-3

当然、カッコを使った電話番号は検索に失敗しています。

Screen Shot 2016-02-01 at 5.16.25.png

今回もやはり法則(パターン)を見つけることから始めます。
今回の場合は、以下のような法則があると言えるでしょう。

「半角数字が2個~5個、ハイフンまたはカッコ(開き)、半角数字が1個~4個、ハイフンまたはカッコ(閉じ)、半角数字が4個」

新しく登場したのは「ハイフンまたはカッコ(開き)」と、「ハイフンまたはカッコ(閉じ)」という「AまたはB」のパターンです。

「AまたはBのいずれか1文字」表す場合は [AB] と書きます(文字の集合を表すので、これも 文字クラス の一種です)。
[ ]の中の文字数に制限はありません。
[ABC] と書けば、「AまたはBまたはCのいずれか1文字」の意味になります。

ハイフンまたはカッコ(開き)と、ハイフンまたはカッコ(閉じ)はそれぞれ、[-(][-)] と表現します。
記号だけが並んでいてちょっとわかりづらいですが、 [AB] の形式になっていることを確認してください。

これをふまえて正規表現を書き直すと次のようになります。

\d{2,5}[-(]\d{1,4}[-)]\d{4}

これをRubularに貼り付けると・・・ちゃんと電話番号を検索できました!

Screen Shot 2016-02-01 at 5.24.31.png

正規表現の知識がないと \d{2,5}[-(]\d{1,4}[-)]\d{4} は謎の暗号にしか見えませんが、ここまでこの記事を読んできた人であれば、メタ文字の意味と、メタ文字ではない普通の文字の区別が付くはずです。

ちなみに今回出てきた半角の丸カッコ( () )は、[ ] の外で使われると特別な意味を持ちます。
それについては次回以降の記事で説明する予定です。

やろうと思えば電話番号を使って、まだまだいろんな正規表現を試すことができるのですが、どんどん記事が長くなってしまうので、電話番号を使った正規表現はいったんここで終了します。

[ ] 中のハイフンは注意が必要

先ほどの正規表現では [-(][-)] と書いて「ハイフンまたはカッコ(開き)」と「ハイフンまたはカッコ(閉じ)」を表現しました。
一般的に [AB][BA] と書いても同じ動きになります。

ただし、- だけは特別な意味を持つので注意が必要です。
実は [a-z] と書くと、「aまたはbまたはcまたは・・・yまたはz」の意味になるのです。
「aまたはハイフンまたはz」ではありません!
同様に、[a-zA-Z0-9] であれば「aまたはbまたは・・・z、AまたはBまたは・・・Z、0または1または・・・9」の意味になります。(端的に言うと、これは「半角英数字1文字」の意味です)

つまり、[a-z] のようなのハイフンは「文字の範囲」を意味します。

ただし、[-az][az-] のように、ハイフンが [ ] の最初、または最後に置かれると「ハイフン1文字」の意味に変わります。
なので [-az][az-] は「aまたはzまたはハイフンのいずれか1文字」の意味になります。

実際に試してみましょう。
Rubularに以下のテキストを貼り付けてください。

名前:伊藤淳一
電話:03(1234)5678
電話:090-1234-5678
電話:0795(12)3456
電話:04992-1-2345
住所:兵庫県西脇市板波町1-2-3

[0-9] という正規表現を入力すると、半角数字のみにマッチします(つまり \d と同じです)。

Kobito.YFpcil.png

一方、[-09] と入力すると、「0または9またはハイフン」にマッチします。

Screen Shot 2016-02-01 at 7.44.42.png

このように、 [ ] の中では使い方によってハイフンの意味が変わることを覚えておいてください。

検索の精度と正規表現の複雑さについて

今回は電話番号を検索するのに \d{2,5}[-(]\d{1,4}[-)]\d{4} という正規表現を使いました。
しかし、これはあくまで「半角数字が2個~5個、ハイフンまたはカッコ(開き)、半角数字が1個~4個、ハイフンまたはカッコ(閉じ)、半角数字が4個」という文字列にマッチするだけです。
電話番号だけを完璧に抜き出す正規表現では決してありません。

検索対象の文字列によっては電話番号以外の文字列にマッチする場合もあります。
たとえば "9999-99-9999" は実在しない電話番号でしょうし、 "03(1234-5678" のように、カッコとハイフンが混在しているおかしな文字列にもマッチしてしまいます。

Screen Shot 2016-02-01 at 7.51.56.png

こういったイレギュラーなケースをすべて除外しようと思うと、まず電話番号の仕様を完璧に理解し、その仕様を完全に満たす正規表現を作らなければなりません。
そしておそらく、その正規表現はかなり複雑なものになるはずです。

電話番号を完璧に表す正規表現の作り方はちょっとわかりませんが、電話番号の代わりに電子メールアドレスの正規表現を調べてみましょう。
ネットを調べると、RFC(公式な仕様)に準拠した電子メールアドレスの「99.99%正確な正規表現」が公開されていました。

Email Address Regular Expression That 99.99% Works.Kobito.pGamAQ.png

リンク先を見てもらうとわかるとおり、非常に複雑な正規表現になっています。

毎回こんな複雑な正規表現を考えている時間はないでしょうし、もしうまく動かなかったときに正規表現を読み解いてデバッグするのもほぼ不可能です。

というわけで、現実的には「100%完璧ではなく、そこそこ正しいレベルの正規表現」で妥協することがよくあります。
そして、「そこそこ正しい」の度合いは要件によって変わってきます。

たとえば、大量のテキストから「電話番号っぽい文字列」を抽出して、人間が目視で判断するのであれば、今回作った \d{2,5}[-(]\d{1,4}[-)]\d{4} でも十分でしょう。

しかし、Webフォームの入力値チェックに正規表現を使うのであれば、もう少し精度を上げるべきかもしれません。
たとえば、「1文字目はゼロ、2文字目はゼロ以外の半角数字」とは言えそうなので、

0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{4}

にすれば、少し精度が上がります。
ご覧の通り、"9999-99-9999" はマッチしなくなりました(ですが、"03(1234-5678" にはまだマッチしています)。

Kobito.8p4v4Q.png

RubyやJavaScriptといった、プログラミング言語中で正規表現を使うのであれば、「正規表現を使ってそこそこの精度で文字列を抽出し、残りはプログラミング言語の機能を使って精度を上げる」というハイブリッドなアプローチも使うのもアリです。

たとえば、以下のRubyのコードでは grep メソッドを使って "03(1234-5678" を除外しています。

text = <<-TEXT
名前:伊藤淳一
電話:03(1234)5678
??:9999-99-9999
??:03(1234-5678
住所:兵庫県西脇市板波町1-2-3
TEXT
numbers = text.scan(/0[1-9]\d{0,3}[-(]\d{1,4}[-)]\d{4}/)
# => ["03(1234)5678", "03(1234-5678"]
numbers.grep(/\(\d+\)|-\d+-/)
# => ["03(1234)5678"]

なお、grep メソッドの引数にも正規表現が登場しています(\(\d+\)|-\d+-)。
この正規表現では今回の記事では紹介していないテクニックを使っていますが、こちらは次回以降の記事で説明する予定です。

まとめ

本記事では正規表現について以下のようなことを学びました。

  • \d は「半角数字1文字」を表す
  • {n,m} は「直前の文字が n 文字以上、m 文字以下」であることを表す
  • {n} は「直前の文字がちょうど n 文字」であることを表す
  • [AB] は「AまたはBが1文字」であることを表す
  • [a-z][-az] ではハイフンの意味が異なる
  • 正規表現の正確さと複雑さはトレードオフになることが多い

今回は「電話番号を検索する」というテーマだったので、まだまだバリエーションに乏しいです。
実務で思い通りに文字列を検索するためには、もう少したくさん正規表現のルールを覚えなくてはなりません。

ただ、初心者の方は今回の記事では正規表現を使いこなすことよりもむしろ、次のようなことを感じ取ってもらえればまずはOKだと思います。

  • 今まではテキストエディタで普通の文字列検索しか使ってこなかったけど、正規表現を使えば今まで目視で調べてたような文字列も検索できるじゃないか!
  • 今まで文字列処理するプログラムでは1文字ずつループさせたり、特定の文字でsplitしたり、あれこれ面倒なことをやってきたけど、正規表現を使ったらそんなことせずに一発で処理できるのでは?
  • 今まで正規表現を見ても意味不明な呪文にしか見えなかったけど、この記事を読んだら正規表現の見え方が変わってきたぞ!?

・・・こんなふうに感じてもらえれば、次回以降の記事でどんどん新しい知識を吸収できるはずです!

ちなみに次回以降は次のようなメタ文字や処理を説明する予定です。

  • よく使いそうな各種のメタ文字(., ?, *, +, |, \w, ^, $, \A, \z, [^], (, ) 等)
  • メタ文字のエスケープ
  • キャプチャを利用した文字列置換

また、この記事をストックしてもらえると、新しい記事を公開したときに更新を通知しますので、よかったらストックしてやってください。

それでは今回はこのへんで!

2016.02.23追記:すべての記事を公開しました!
すべての記事を公開しました。全4回です。
どんどん実践的な内容になっていくので、こちらも続けて読んでみてください。

初心者歓迎!手と目で覚える正規表現入門・その2「微妙な違いを許容しつつ置換しよう」 - Qiita

説明する内容

  • ?.+*\w[^AB] の意味
  • ( ) を利用したキャプチャと置換
  • (?: ) を利用したキャプチャ無しのグループ化
  • 特別な文字のエスケープ
  • +* を使用する際の注意点

初心者歓迎!手と目で覚える正規表現入門・その3「空白文字を自由自在に操ろう」 - Qiita

説明する内容

  • ^$\s\t\n の意味
  • | を使ったOR検索
  • 環境によって異なる改行コードと正規表現の関係
  • 使われる場所によって異なる ^ の意味

初心者歓迎!手と目で覚える正規表現入門・その4(最終回)「中級者テクニックをマスターしよう」 - Qiita

説明する内容

  • \b の意味
  • 肯定の先読み、後読み
  • 否定の先読み、後読み
  • 後方参照
  • メタ文字の複雑な組み合わせ
  • 正規表現とパフォーマンス
  • メタ文字のエスケープ
  • [ ] 内のメタ文字の働き
  • {n,}{,n} の意味
  • \W\S\D\B の意味

正規表現を学習するのにオススメの本

正規表現をしっかり学ぶなら、オライリージャパンの「詳説 正規表現」がオススメです。
僕もこの本を読んで正規表現を学習しました。

詳説 正規表現 第3版
51cRxtwo7IL.jpg