開発ブログ

WWWクリエイターズが送る、Git、CSS、HTML、コマンドライン、Macの便利機能など、開発に関する役立ち情報発信します。気まぐれに更新。

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

最終更新:2017-09-28 by Joe

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

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

最短一致でマッチさせる

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

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

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

正規表現の両指定子

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

量指定子(最長一致)最短一致意味
**?直前のパターンの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>」と言った正規表現が思いつきます。ですが、これだと「最長一致」を探してしまい、その文字列すべて(すべてのアンカータグをひと続きで)をマッチしてしまいます。

ここで、上記のNon-greedyなマッチングを利用します。「?」記号を利用した最短一致でマッチさせれば、個々のアンカー要素を取り出すことができそうです。

下記は個々のアンカータグにマッチします。

// Non-greedy なマッチングで個々のアンカータグを検出
<a.+?>(.+?)<\/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

 

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

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

現場からは以上です。

 

閉じる