Help us understand the problem. What is going on with this article?

[AutoHotKey]ある正規表現パターンにマッチする全ての部分を返す関数

More than 1 year has passed since last update.

概要

grep -oみたいなことをやりたい。

例えば、aa11bb22cc11ddという文字列があるとして、
正規表現[a-z]{2}11にマッチする部分aa11 cc11を両方返してほしい。

RegExMatch()ってどうなの

AutoHotKeyで正規表現にマッチする文字列を得るといえばRegExMatch()

しかしこれだと、与えた正規表現にマッチする文字列の中の()で括られたサブマッチ文字列しか得ることができない。
言い換えると、得たい部分の全てを含む文字列にマッチする正規表現を与える必要がある。

さっきの例でいうと、aa11 cc11の両方を得るには、以下のような記述になる。

sample.ahk
InputVar = aa11bb22cc11dd
RegExMatch(InputVar, "([a-z]{2}11).+([a-z]{2}11)",Match)

MsgBox, , %A_scriptname%,
(
%Match1%
%Match2%
)

ややこしいし、マッチする数が多いほど正規表現が際限なく長くなり、さらにマッチする数がわからない場合にはどうしようもない。

どうすれバインダー。

開始位置をシフトする

RegExMatch()には、検索を開始する位置(文字数)を指定することができる。
また、返り値としてマッチした位置(マッチした部分の最初の文字が全体で何文字目か)も得ることができる。
さらに、マッチした文字列をStrLen()に突っ込めば、その文字数も得ることができる。

上の例で言えば、最初にマッチする部分aa11については、以下のようになる。

項目 結果
検索を開始した位置 1
マッチした位置 1
マッチした文字数 4
マッチした位置と文字数の和 5

ということは……
同じ正規表現パターンで、5文字目から検索を開始したら、次の一致箇所を得られるということではないか。

ループする

開始位置をシフトしながらループするという方向性が見えた。

正規表現にマッチしなかった場合、RegExMatch()は0を返すので、Whileループを用いれば、マッチしなくなるまでループできる。

マッチした文字列は配列に食わせておけば、あとで(マッチした数も含めて)適切に参照できる。

さっきの例で言えば、以下のようなコードになる。

sample.ahk
Matches := object() ; マッチした文字列を格納する配列オブジェクトを用意
InputVar = aa11bb22cc11dd
pos = 1 ; スタート位置を1文字目に設定

While (pos != 0) ; マッチしなかったら終了
{
    ; マッチした文字列を変数`Match1`、マッチした位置を`tpos`に格納
    tpos := RegExMatch(InputVar, "([a-z]{2}11)",Match,pos)
    ; マッチした位置+マッチした文字数分開始位置をシフト
    pos := (tpos + StrLen(Match1))
    ; マッチしていたら文字列を配列に挿入
    If (pos != 0)
        Matches.insert(Match1)
}
for index, Match in Matches
{
    MsgBox, , %A_scriptname%, %Match%
}

RegExMatch()の返り値は「検索開始位置からの距離」ではなく「文字列先頭からの距離」なので、1ループごとにposに加算していく必要はない。

この例だと正規表現全体が()で括られているので%Match% = %Match1%なのだが、例えばパターンが[a-z](11[a-z]{2})[a-z]とかになると違ってくる。
「前後を特定のなにかに挟まれた部分」とかを抜き出したい場合も多いので、概ね%Match1%の方を抜き出すことになると思う。

関数化してみる

上記記事でやったように、配列を戻り値とする関数にするのが適切だろう。

必要なパラメータは、元になる文字列と正規表現パターンの2つ。

StringDraw.ahk
StringDraw(Input,Pattern)
{
    Matches := object()
    pos = 1
    While (pos != 0)
    {
        tpos := RegExMatch(Input, Pattern, Match, pos)
        pos := (tpos + StrLen(Match1))
        If (pos != 0)
            Matches.insert(Match1)
    }
    Return, Matches
}

/* 使用例
for index, Match in StringDraw("aa11bb22cc11dd","([a-z]{2}11)")
{
    MsgBox, , %A_scriptname%, %Match%
}
*/

思いつくまで悩んだけど、出てきたコードは大して複雑でもなかった。

可変長引数対応

パターンが複数欲しい時があったので可変長引数にしてみた。

StringDraw.ahk
StringDraw(Input,Pattern*)
{
    Matches := object()
    for index, Pat in Pattern
    {
        pos = 1
        While (pos != 0)
        {
            tpos := RegExMatch(Input, Pat, Match, pos)
            pos := (tpos + StrLen(Match1))
            If (pos != 0)
                Matches.insert(Match1)
        }
    }
    Return, Matches
}

第2引数以降が正規表現パターンで、各パターンでマッチした文字列を全てひとつの配列にまとめて返す。

catfist
物書き志望です。
https://wbtmiu.herokuapp.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away