正規表現の最短マッチを覚える

今さらながら正規表現の「最短マッチ」を紹介。


「HTML 文字列を対象に href 属性の値だけを正規表現で取得したいなー」

と思った時に、以下のように書いた。

const text = '<a href="index.html">Index</a>';
const match = text.match((/href="(.*)"/u))[1];
console.log(match);
// → index.html

うむ、うまく行った。

…と思いきや、次のような文字列だと思ったようにいかない。

const text = '<a href="index.html" target="_blank">Index</a>';  // ← ココが違う
const match = text.match((/href="(.*)"/u))[1];
console.log(match);
// → index.html" target="_blank
//                ↑ あれ?

href 属性の終わりのダブルクォートではなく、その後ろに続く target 属性の終わりのダブルクォートまでが取得されてしまった。

正規表現は、デフォルトでは「欲張りなマッチ (Greedy Match)」と呼ばれる挙動をするそうだ。すなわち「できるだけ長くマッチした文字列を拾う」ので、

↑このようにダブルクォートに囲まれた文字列を検索しようとした時に、なるべく長くマッチするよう調整されて、target 属性の終わりのところにあるダブルクォート文字までが拾われてしまったのだった。

.* とは書いたものの、すぐ次に登場するダブルクォートまでを対象にしたいんだよ〜! という時は、正規表現の最短マッチという仕組みを使う。

書き方は簡単で、.* と書いた部分を .*? と書くだけ。

実際に違いを見てみよう。

まずは「欲張りなマッチ」の挙動をおさらい。

[
  '<a href="index.html">Index</a>',
  '<a href="index.html" target="_blank">Index</a>',
  '<a href="index.html">Index</a> | <a href="top.html">Top</a>'
].forEach((text) => {
  const match = text.match((/href="(.*)"/u))[1];
  console.log(match);
});

// → index.html
// → index.html" target="_blank
// → index.html">Index</a> | <a href="top.html

続いて最短マッチ。

[
  '<a href="index.html">Index</a>',
  '<a href="index.html" target="_blank">Index</a>',
  '<a href="index.html">Index</a> | <a href="top.html">Top</a>'
].forEach((text) => {
  const match = text.match((/href="(.*?)"/u))[1];  // ← 最短マッチ .*? を利用
  console.log(match);
});

// → index.html
// → index.html
// → index.html

最短マッチなら、キチンと href 属性の値部分が取得できた。