uhyohyo.net

JavaScript初級者から中級者になろう

四章第三回 正規表現2

正規表現の利用

さて、正規表現は、ただその文字列がパターンにマッチするかどうかではなく、実はさまざまなことに利用できます。

match

matchというものがあります。これは、前回のtestと同じように、正規表現と文字列をマッチング(マッチさせること)するものです。

testの返り値はマッチしたかどうかの真偽値でしたが、matchの戻り値は違います。

matchの戻り値は配列です。この配列の0番目の要素には、マッチした文字列が入っています。

マッチした文字列とはどういうことかというと、例えば、a.{5}cという正規表現が、

aabcbcacbuccb
 ~~~~~~~
          

という部分にマッチしたとき、この配列の0番目には"abcbcac"が入っています。

ちなみに、この正規表現は、「aとcの間に何かが5文字入っている」という正規表現ですね。

さて、では具体的にどう書くかというと、

文字列.match(正規表現オブジェクト)

というように書きます。前回のtestとは逆なので、注意しましょう。testでは引数に渡すのが文字列だったのに対し、今回引数に渡すのは正規表現オブジェクトになっています。

では、それなら普通に文字列を返せばいいのに、なぜ配列になっているのでしょうか。実は、1番目以降の要素も使われる場合があるのです。

どういうときに使われるかというと、前回解説したグループ化と関わりがあります。グループ化とは、一部分を括弧で囲んでひとまとめにすることでした。

実は、このようにグループ化したとき、マッチした部分のうち、さらにそのグループ化した部分だけを個別に取得することができます。例えば、上のa.{5}cを、a(.{5})cとした場合、括弧で囲んだ.{5}の部分だけを、マッチした全体とは別に取得できます。

つまり、この正規表現が

aabcbcacbuccb
 ~~~~~~~
          

という部分にマッチしたとき、matchの返り値である配列の0番目の要素はそれ全体である"abcbcac"です。ここで、配列の1番目に入っているのは、括弧で囲んだaとcの間の5文字だから、

aabcbcacbuccb
  ~~~~~
          

の部分だけということになります。

JavaScriptで書くと、

var str = "aabcbcacbuccb";
var result = str.match(/a(.{5})c/);
/*
result[0] は "abcbcac"
result[1] は "bcbca"
*/
          

ということになります。

ちなみに、

a(.{5})c(.{3})

のように括弧が2つ以上ある場合も、1つめの括弧の中は1番目の要素に、2つめは2番目・・・のようにいくつでも取得することができます。

さて、これはかなり応用がききます。たとえば、<img src="aaa.gif">のように、開始タグの文字列があったとして、ここからタグ名(今回の場合"img")だけを抜き出したいとします。このとき、どのようにしたらいいと思いますか?

matchを利用して、正規表現を使えばいいのです。いろいろ書き方はありますが、例えばこうです。

<(\S+)\s+.+>

<があって、その次に\S(=空白文字以外)がいくつかあって、その次に空白がいくつかあって、さらに次には任意の文字がいくつかあって、最後に>がある」という意味の正規表現ですね。

上のタグの場合だと、<の部分が最初の<で、次のimg(\S+)に当てはまり、次のスペースが\s+になります。

src="aaa.gif"の部分は.+が、最後の>には>が当てはまることになります。括弧でグループ化されているのはimgの部分だから、うまくいってますね。

しかし、実はこれはまだ問題があります。

<p>

のように属性などが1つもない場合はどうでしょうか。これには空白などどこにもないから、上の正規表現ではマッチしてくれません。ちなみに、その正規表現がそもそもマッチしないときは、testだとfalseを返したのに対し、matchでは配列のかわりにnullを返します。そのため、resultがnullかどうか調べずにいきなりresult[0]などを得ようとするとエラーになることがあります。

さて、どうすればいいかというと、後ろの空白以降の部分はあってもなくてもいいわけです。言い換えると、0個または1個あるということです。こういうのは、前回紹介しましたね。そう、?です。これは「直前の文字が0個または1個」という意味でした。

しかし、今回は、文字ではなくまとまった文字列です。こういう時どうするかも前回あわせて解説しました。そう、グループ化です。

つまり、

<(\S+)(\s+.+)>

このように空白文字以降の部分をグループ化して、それの後ろに

<(\S+)(\s+.+)?>

このように?をつければよいわけです。

これで一応正規表現のほうはOKです。しかし、ここでできあがった正規表現を改めて見てみましょう。グループ化されたものが2つできています。1つめのグループ化はあとでその部分だけ参照するためで、2つめのグループ化は?を使うためというように目的は違います。

しかし、同じグループ化だから、2つめのグループも、あとで参照できるように配列に入ります。どうせあとで見るつもりもないのに、これは少し無駄だといえます。

そこで、その無駄を解消する方法があります。それは、

<(\S+)(?:\s+.+)?>

このようにすることです。?:という2文字が付け足されました。

実は、( 〜 )で囲むかわりに、(?: 〜 )で囲むと、「グループ化はするけども、後で見ないから結果の配列には入れない」という意味になります。

2つめのグループは別に後で見る必要はないので、このようにすると無駄がなくていいです。

replace

さて、matchの他にも、よく使われるものがあります。その1つがreplaceです。これは、正規表現で検索して、マッチした部分を別の文字列に置き換えるというメソッドです。

こんな感じで使います。

文字列.replace(正規表現オブジェクト,置き換え後文字列)

返り値が、置き換えた後の文字列です。

サンプルは、

var str = "aaa1234bcd567efg8999h";		//もともとの文字列
var result = str.replace(/\d+/, "数字");	//置き換え後の文字列
console.log(result);
          

という感じです。 replaceの引数は、1つめが/\d+/で、2つめが"数字"ですね。

この正規表現オブジェクトは、\dが「数字1文字」を表すから、それが1つ以上ということなので、1234、555、0、29・・・といった、数字が何文字か連なったものにマッチします。よくありがちなパターンです。

マッチしたところが2つめの引数に置き換わるということは、今回

aaa1234bcd567efg8999h
   ~~~~
          

の部分がマッチしたとき、その部分が

aaa数字bcd567efg8999h
   ~~~~
          

のように置き換わるということです。結果、できあがる文字列は"aaa数字bcd567efg8999h"ということになります。

ちなみに、「567」や「8999」の部分も\d+の条件には当てはまるのですが、マッチするときは、原則文字列の前のほうから見ていきます。今回、一番まえにあるのが「1234」だったから、そこがマッチしたのです。

正規表現のオプション

オプションとは、つまり細かい設定ということです。正規表現オブジェクトひとつひとつに、こういった細かい設定を持たせることができます。

iオプション

iオプションというものがあります。このオプションがあると、マッチするときに半角英字の大文字と小文字を区別しなくなります。つまり、例えば/abc/という正規表現が、AbCというような文字列にもマッチするようになります。

では、iオプションはどうやってつけるかというと、正規表現オブジェクトを作るときに、

/正規表現/i

とします。正規表現の終わりを示すスラッシュの後に、iという文字がつきました。これで、iオプションがつきます。

これを、

"ABCDE".match(/abc/i)

のように使えます。

ちなみに、ここで初めて出てきましたが、いちいち変数に文字列を代入しなくても、このように直接matchやreplaceを使えます。

gオプション

もう1つ、gオプションというものがあります。これはiオプションよりも使われると思います。つけ方はiオプションと同じで、iのかわりにgをつけるだけです。

このgオプションがついていると、その正規表現オブジェクトを使ってマッチングしたとき、複数回マッチします

複数回マッチするというのは、上のサンプルの場合、/\d+/がマッチするのはgオプションが無い場合は

aaa1234bcd567efg8999h
   ~~~~
          

の部分だけでしたが、gオプションがつくと、

aaa1234bcd567efg8999h
   ~~~~   ~~~   ~~~~
          

のように当てはまるところ全てにマッチします。

それに伴って、各メソッドの動作も変わります。

replaceの場合簡単で、マッチした部分それぞれが置き換わります。つまり、

"aaa1234bcd567efg8999h".replace(/\d+/g, "数字")

とした場合、

aaa1234bcd567efg8999h
   ~~~~   ~~~   ~~~~
          

のように複数マッチして、

aaa数字bcd数字efg数字h

という文字列が返ってきます。これはよく使うことでしょう。

matchは、返ってくる配列の中身が変わります。gオプションがついていない場合は、上で解説したように0番目の要素にはマッチした文字列全体が、1番目以降にはグループ化した箇所の文字列が入っているのでした。

これが、gオプションがつくと、マッチした文字列が、0番目から順番に入っているようになります。つまり、

"aaa1234bcd567efg8999h".match(/\d+/g)

で返ってくる配列には、この文字列で

aaa1234bcd567efg8999h
   ~~~~   ~~~   ~~~~
          

のようにマッチするから、0番目には"1234"、1番目には"567"、2番目には"8999"が入っていることになります。

この場合、グループ化した部分などは、配列から得られなくなります。

ちなみに、この2つを併用して

/正規表現/ig

という書き方も可能です。

また、前回出てきたtestは、ただマッチするかどうかを調べるだけなので、gオプションがついても動作は変わりません。

replaceとグループ化

上で、replaceでマッチした部分を別の文字列に置き換えるということを解説しました。実は、このとき置き換え後の文字列に、グループ化された部分の文字列を使うことができます。

例えば、上ではreplaceで\d+にマッチする部分を"数字"に置き換えましたが、これを、ただ置き換えるのではなく、"数"と"字"で囲みたいとします。例えば、"1234"という文字列にマッチしたなら、これを"数1234字"というふうに置き換えたいとしましょう。

このとき、次のようにします。

"aaa1234bcd567efg8999h".replace(/(\d+)/g, "数$1字");

置き換え後の文字列に、"$1"という謎の文字があります。実は、これは置き換えるときに、グループ化された部分の文字列に置き換わります。$1は最初のグループ化、$2ならその次、$3、$4…と続きます。

今回gオプションをつけたので、当てはまるところ全てにマッチします。つまり、

aaa1234bcd567efg8999h
   ~~~~   ~~~   ~~~~
          

aaa数1234字bcd数567字efg数8999字h

というように置き換わります。

最長マッチと最短マッチ

最後に最長マッチと最短マッチというものについて解説します。

例えば、上で解説した機能を使って、" 〜 "で囲まれている部分を「 〜 」で囲むように置き換える処理を作ってみたいと思います。

" 〜 "で囲まれている部分を表す正規表現は、次のようになりますね。

".*"

最初と最後に"があって、その間は.*だから、任意の文字が何個かあるか、もしくはないということです。

囲まれている部分は置き換え時に使うので、

"(.*)"

のようにグループ化します。replaceで実際に置き換えると、

文字列.replace(/"(.*)"/g, "「$1」")

という形になります。複数" 〜 "で囲まれた部分があった場合全て置き換えたいということで、今回はgオプションをつけてみました。

さて、これでいいように思えますが、実は問題もあります。実際に、" 〜 "で囲まれた文字列が2つある

"aaaaa".  "bbbbb".

という文字列を置き換えることを考えてみます。まず"(.*)"がマッチする部分ですが、当然

"aaaaa".  "bbbbb".
~~~~~~~   ~~~~~~~
          

のように2つマッチしてほしいわけです。ところが、実際は違います

"aaaaa".  "bbbbb".
~~~~~~~~~~~~~~~~~
          

このようにマッチします。確かに、これ全体をひとかたまりにしてみると、"で始まって、"で終わっています。この場合、.*にあてはまるのは、最初と最後の"を除いた

aaaaa".  "bbbbb

の部分です。

このようになるのは、マッチするとき、なるべく長くなるようにマッチするという性質によるものです。この性質を、最長マッチなどといいます。

これを改善する方法は、2つあります。まず、今回説明したい方から解説します。

それは、この最長マッチという性質を変えるという方法です。理想的なのは、なるべく短くなるようにマッチするというような性質です。この性質は、最短マッチなどとよばれます。

実は、最長マッチをこの最短マッチに変える方法はちゃんとあります。それは、+、*などの、繰り返しを表す記号の直後に、?をつけます。注意してほしいのが、この?は、「直前の文字が0回または1回」という意味のそれとは別物であるということです。あくまで、+や*などの繰り返しの記号のあとに?をつけるのです。

つまり、

"(.*?)"

ということになります。今回余計に多くの文字をマッチさせたのがこの繰り返しの部分だから、ここを最短マッチにします。

"aaaaa".  "bbbbb".
~
          

最初の"があって、その後.*の部分は

"aaaaa".  "bbbbb".
 ~~~~~~~~~~~~~~~
          

というようにマッチするよりも

"aaaaa".  "bbbbb".
 ~~~~~
          

までしかマッチしないほうが短いから、最短マッチなのでこのようにマッチすることになります。すると最後の"はその直後の"にマッチし、結果

"aaaaa".  "bbbbb".
~~~~~~~
          

の部分が全体としてマッチするわけです。

また、今回gオプションがついているので、今回まだ"bbbbb"の部分がマッチしないで残っているから、この部分も同じようにマッチしてくれます。

この最短マッチもけっこうよく使うものです。

さて、2つめの方法ですが、こっちはけっこう単純です。

.*の部分が、任意の文字だからといって、"が出てきてもそれも含んでどんどん先までマッチしてしまったことが原因だから、この.が0回以上繰り返すというものを、"以外の文字が0回以上繰り返すというようにすればいいのです。

すると、この" 〜 "の間の文字に"が含まれることはなくなるので、正しくマッチします。

"([^"]*)"

のようになりますね。