正規表現:最短一致でマッチさせる表現

最終更新:2018-08-09 by Joe

正規表現を使って「最短一致」でマッチングを取ります。

通常、正規表現では、「*」などの量指定子を利用してマッチを検出した時、「最長一致」を検出しますが、特定の記述方法によって、パターンの「最短一致」を表現することができます。

最短一致でマッチさせる正規表現

最長一致と最短一致

正規表現での「*」「+」「?」「{n}」などの量指定子(Quantifier)は、文字、文字クラスやサブパターン、またはメタ文字などの後ろにくっつけることで、「文字の繰り返し」を表現できるのはよく知られています。

このとき、量指定子はデフォルトでは「Greedy(欲張り)なマッチ」を行う仕様となっており、可能な限り長い文字列のマッチを探してしまいます。

そのような量指定子の直後に「?」を置くことで、逆に「Non-greedy(最短)なマッチ」を表現でき、可能な限り短い文字列のマッチ、すなわち最短マッチを検出するようになります。

// 「PATTERN」の最長一致
/PATTERN*/

// 「PATTERN」の最短一致
/PATTERN*?/

正規表現の量指定子

量指定子には下記のようなものがあります。

量指定子(最長一致)最短一致意味
**?直前のパターンの0回以上連続
++?直前のパターンの1回以上連続
???直前のパターンの0回か1回の出現
{N} - 直前のパターンのN回の連続
{min,}{min,}?直前のパターンのmin回以上の連続
{,max}{,max}?直前のパターンのmax回以下の連続
{min,max}{min,max}?直前のパターンのmin回からmax回の連続

※Vimのmagicを利用した正規表現は、記述方法が異なりますので、ご注意下さい。こちら「 正規表現:特殊文字(メタ文字)の一覧 」をご覧ください。

最短マッチを見つける正規表現

さて、具体例を見ていきましょう。例えば、から<a></a>で囲まれたアンカータグを「1つだけ」取り出す事を考えます

<nav>
  <a href="/about">About</a> | <a href="/access">Access</a> | <a href="/contact">Contact</a>
</nav>

改行以外のすべての文字を表す「.」を利用して、「<a . *</a>」と言った正規表現が思いつきます。ですが、これだと「最長一致」を探してしまい、上記の例だと、3つのアンカータグをひと続きでマッチしてしまいます。

// アンカータグを検出?(デリミタに#を利用)
#<a.*>(.*)</a>#

// マッチする文字列
<a href="/about">About</a> | <a href="/access">Access</a> |<a href="/contact">Contact</a>

ここで、上記の Non-greedyな最短マッチングを利用します。

「?」記号を利用した「.*?」で、最短一致の繰り返しをマッチさせれば、個々のアンカー要素を取り出すことができそうです。

// アンカータグを最短一致で検出
#<a.*?>(.*?)</a>#

// マッチする文字列
<a href="/about">About</a>

最短一致の実際の表現例

最短のマッチングの実際の使用について、下記にいくつかの例を見てみます。

Javascript

RegExpクラスの「testメソッド(booleanを返す)」「execメソッド(nullか、もしくはマッチ文字列の配列を返す)」を使えます。

var re = new RegExp("<a.+?</a>");
var result = re.exec('<a href="/x">xx</a><a href="/y">yy</a>')

if (result === null) {
  alert('見つかりませんでした・・。');
} else {
  // 最初のアンカータグを取り出せる
  alert('最初のアンカーは、' + result[0] + 'です!');
}

なお、Javasciptでは、正規表現リテラルを使って、「var re = /<a.*?<\/a>/;」とも表現できますが、上記のようにnewを利用するとデリミタを書く必要がないので、HTMLタグにおけるスラッシュをエスケープしなくて済みます。(Javascriptでは、デリミタに「/」以外が使えないのです)お好みでどうぞ。

PHP

preg_xxx系の関数は色々あります。(PHPマニュアル:preg_match)下記は最も基本的な、マッチング結果をbooleanで返す関数の例です。

if ( preg_match( '#(<a.*+</a>)#', '<a href="/x">xx</a><a href="/y">yy</a>', $m ) ) {
  echo "ありました。最初のアンカーは、" . $m[1] . "です。";
}

引数の使い方に注意します。3番目の変数に、後方参照用の配列を渡します。見つかれば、その配列の、N番目の要素に、マッチのN番目が入ります。

また、通常は、デリミタを「/ (スラッシュ)」で記述しますが、代わりに「#」を利用することで、スラッシュをエスケープする必要がなくなります。これは便利ですよね。デリミタは英文字やバックスラッシュ、スペース以外ならなんでもいいようです。

筆者は「#」をよく使います。(お好みでどうぞ。)

Vim

Vim の magic による正規表現はクセがあるので注意が必要です。

magicにおける0回以上の繰り返しの最短マッチは「\{-}」です。

// 例1:カウントを返す
%s#<a.\{-}</a>#&#gn

ちなみに、末尾に「n」をつけることで、文字列置換を実際には行いません。マッチのカウントをVIM上に表示だけするためにこれ付けています。

下の例は、すこし複雑に見えますが、後方参照により、アンカータグだけを取り除きます。

// 例2:アンカータグを取り除く
%s#<a.\{-}>\(.\{-}\)</a>#\1#g

正規表現に関する参考リンク

さて、うまく最短マッチの正規表現がかけたでしょうか?

現場からは以上です。