レンタルサーバー速度比較記事はこちら

RegExp.execにハマった。無限ループの回避方法

JavaScriptの正規表現で、マッチする文字列を検索するexecメソッドでハマったのでメモ。

正規表現に一致する文字全てを抽出したい場合、RegExpにgフラグ(グローバルサーチ)オプションを付けて実行します。
var regex1 = RegExp('foo*','g');

あとはexecメソッドを実行するだけ、と思いきや…。

ハマりポイントその1

execメソッド一回の実行につき1つしか文字列が取得できません。
戻り値で配列が返ってくるんだから、てっきり一致する文字列が全て列挙されてるのかと思ってました。

参考 RegExp.prototype.exec()MDN web docs

MDNのサンプルを参考にwhile文で回して取得します。
マッチする文字が無くなるとnullが返ってきます。

コード
let regex = RegExp('foo*','g');
let str = 'table football, foosball';
let result_array;
while ((result_array = regex.exec(str)) !== null) {
	console.log('マッチ:' + result_array[0]);
}

ハマりポイントその2

うっかり空文字とマッチすると無限ループ。
先ほどのサンプルを参考に実装していたのですが、うっかり空文字とマッチしてしまうパターンを書くとindexが進まなくなり無限ループになってしまいました。

コード
let regex = RegExp('.*?(?=ドーム)', 'g');
let str = '東京ドーム, 福岡ドーム, 大阪ドーム, ナゴヤドーム, 西武ドーム, 札幌ドーム';
let result_array;
while ((result_array = regex.exec(str)) !== null) {
	console.log('ドーム所在地:' + result_array[0]);
}

上記コードは「ドーム」の前の文字を取得したくて書いたのですが(無限ループ以前に .*?(?=ドーム) だと意図した結果にはなりませんが、それは置いといて)、

  1. 一度目のexec実行では意図通り '東京' の文字列が取得できます。このときに'東京'の文字分だけregexのindexが進み 2 になります。
  2. 二度目以降は量指定子が * (0回以上の繰り返し)になっているため、そのまま index 2 の位置で条件一致。ただの空文字('')が取得できてしまいます。
  3. 文字列長が0のためindexが進まず、三度目以降もずっとindexが2のまま無限ループ。

.*?(?=ドーム) だったのを .+?(?=ドーム) 「1回以上の繰り返し」にすれば回避できるのですが、正規表現パターンを間違えるたびに無限ループになるのは大変なので、空文字チェックも加えて無限ループになるのを避けられるように修正しました。

コード
let regex = RegExp('.*?(?=ドーム)', 'g');
let str = '東京ドーム, 福岡ドーム, 大阪ドーム, ナゴヤドーム, 西武ドーム, 札幌ドーム';
let result_array;
while ((result_array = regex.exec(str)) !== null && result_array[0] != '') {
	console.log('ドーム所在地:' + result_array[0]);
}

while文の繰り返し条件にresult_array[0] != '' で空文字チェックを加えました。
これなら永久パターンを書いてしまっても大丈夫。

そして最後に。
正しく所在地が取得できるパターンを書くと
[^(, ドーム)].+?(?=ドーム)
でした。


正規表現の確認が出来るツールを作ったので是非こちらもチェックしてみてください。

JavaScript用の正規表現一括テストツールを作成しました

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です