RCIE-ジャンクのコード屋

主に自分のためにコーディングのTIPSを蓄積しています。

VBA 正規表現の入門 RegExp

対象者

VBAを使っている人、かつ正規表現を使ってみたい人。

正規表現 (Regular Expression) は何に使うのか

正規表現を使うと、いろんなパターンの文字列を一度に検索することができます。
例えば、

  • 文系の学生
  • 文系の生徒
  • 理系の学生
  • 理系の生徒

の4パターンを(文|理)系の(生徒|学生)と検索できるので、4回検索する手間を1回に節約できます。
正規表現を覚えると、もう、正規表現なしで文字列を検索することは無くなります……というのは大げさですが、それくらい便利です。



正規表現のはじめかた

  1. ツールバーから、[ツール]>[参照設定]
  2. 参照設定から、[Microsoft VBScript Regular Expressions 5.5]>[OK]

コードには以下のように記述してください。

Const s文章 = "3年理系の生徒は手続きをしてください。" '検索対象

Dim rx As New RegExp
rx.Patten = "(文|理)系の(学生|生徒)"    '検索パターン

If rx.Test(s文章) Then
	MsgBox "合致しました"    '※今回は合致する
Else
	MsgBox "合致しませんでした"
End If

文章を変えながら、パターンに合致するか試してみましょう。


文法レベル1

最も理解しやすいものから順に解説します。

指定した文字列のどれかに合致
  • 野いちご
  • 野イチゴ
  • 野苺

(いちご|イチゴ|)
それぞれの文字列を | で区切って、( ) で囲みます。


指定した文字の内のどれか(1文字)
  • タイプA
  • タイプB
  • タイプC

タイプ[ABC]
文字を並べて [ ] で囲みます。


文字列があってもなくてもいい
  • 生鮮売場担当者
  • 生鮮担当者

生鮮(売場)?担当者
あってもなくてもいい文字列を ()? で囲みます。


文字があってもなくてもいい(1文字)
  • お代官様
  • お代官
  • 代官様
  • 代官

お?代官様?
あってもなくてもいい文字の後ろに ? をつけます。


なんでもいい1文字
  • かつら
  • かしら
  • かめら

.
なんでもいい1文字は . (ドット)で表せます。


指定した文字以外なら、なんでもいい
  • 山田太郎
  • 山田※これは除外
  • 山田※これは除外
  • 山田四郎

山田[^次三]
除外したい文字を [^] で囲みます。


数字1文字なら、なんでもいい
  • 4番線
  • 6番線
  • 8番線

\d番線
数字1文字は \d で表します。
d はデジタルの d でしょうか。


A~Z なら、なんでもいい
  • Aランク
  • Fランク
  • Zランク

[A-Z]ランク
文字の範囲指定は [x-y] のように書きます。


何回くりかえしてもいい
  • Aランク
  • AAランク
  • AAAAAランク
  • ランク

A*ランク
何回くりかえしてもいい(0回もOK)場合は * を使います。


何回くりかえしてもいい(1回以上)
  • Aランク
  • AAランク
  • AAAAAランク
  • ランク ※これは除外

A+ランク
何回くりかえしてもいい(0回はダメ)場合は + を使います。


文法レベル2

~で始まり~で終わる
  • 最強勇者の俺が錬金術師にジョブチェンジした件について
  • 最強勇者の俺が転生したら皆が最強勇者だった件について
  • 最強勇者の爺様が大魔王から好かれすぎて限界な件について
  • 最強勇者の件について

最強勇者の.*件について
「最強勇者の」と「件について」の間は、なんでもいい文字 ( . ) を何回くりかえしてもいい ( * ) ということです。


アルファベットや数字なら何でもいい
  • Aキー
  • 8キー
  • yキー

[A-Za-z0-9]キー
文字の範囲指定 [x-y] は、複数まとめて書くことができます。


n回くりかえす
  • アアイアア
  • アイウアイ
  • ウイイウア

[アイウ]{5}
アイウをくりかえしている回数5を { } で囲みます。


n回以上くりかえす
  • 1234567890
  • 31415926
  • 223609

\d{6,}
6回やそれ以上の場合、6の後ろにカンマが入ります。


n回以下くりかえす
  • 背番号
  • 背番号5
  • 背番号777

背番号\d{,3}
3回やそれ以下の場合、3の前にカンマが入ります。
1~3回くりかえす場合は、{1,3} と書きます。


特殊な文字と合致

( | ) ? . [ ] * + { } \ ^ $ といった文字は、正規表現の条件を表すために使われる特殊な文字です。

  • 温泉(男湯)
  • 温泉(女湯)
  • 温泉(混浴)
  • 温泉(露天)

温泉\(..\)
( を検索するには、\( と書きます。同様に、? を検索するには \? と書きます。他の文字も同じように \ を前につけます。


合致した文字列から一部を取り出す (サブマッチ)

このようなログがあるとします。

  • 製造年月:2022/05, ID:1242531_JP, 検品済

ここからIDの部分を検索して、

  • ID:1242531_JP

(数値n桁) と (国名アルファベットn桁) を取り出したいと思います。
取り出したい部分(数値と国名)をカッコで囲んで正規表現を作ります。
ID:(\d+)_([A-Z]+)
そして、次のようなVBAのコードを書きます。

Const s文字列 = "製造年月:2022/05, ID:1242531_JP, 検品済"    '検索対象

Dim rx As New RegExp
rx.Patten = "ID:(\d+)_([A-Z]+)"    '検索パターン

Dim s数値 As String
Dim s国名 As String

Dim m As Match
For Each m In rx.Execute(s文字列)    '検索を実行して、結果をmに取り出す
	s数値 = m.SubMatches(0)    '0番のサブマッチ(\d+)
	s国名 = m.SubMatches(1)    '1番のサブマッチ([A-Z]+)
Next
MsgBox "数値=" & s数値 & " , " & "国名=" & s国名    '数値=1242521 , 国名=JP

正規表現の中で ( ) に囲まれた部分は、m.SubMatches(n) で取り出せます。
Sheets(n) などと異なり、連番が0から始まるので注意です。


文法レベル3

できるだけ短い合致を探せ / できるだけ長い合致を探せ

ここにHTMLのタグがあります。

  • <body><a href="URL">リンクはこちら</a>本文......

最初に出現したタグ <body>を取り出したいと思います。正規表現は以下のようになります。
<.+?>
+? というのは、「できるだけ短く合致する箇所を探せ」という意味です。控え目ですね。

今までのように単純に + を使って検索すると…
<.+>

  • <body><a href="URL">リンクはこちら</a>本文......

ここが選ばれてしまいます。
+ というのは、「できるだけ長く合致する箇所を探せ」という意味だったのです。欲張りですね。
控え目な方が処理も軽いので、目的に沿っていれば +? を使っていきましょう。


行頭と行末

行の中に「~円」以外に余計なものが入っていないものを選びたいと思います。

  • 880円
  • 1410円
  • 1980円です。 ※これは除外
  • 合計:2320円 ※これも除外

^\d+円$
^ は「ここが行頭になる」という意味です。
$ は「ここが行末になる」という意味です。


指定した文字列以外なら、なんでもいい
  • 山田昭雄
  • 山田一郎 ※これは除外
  • 山田梅子
  • 山田エリザベス

山田(?!一郎).+
この位置に 一郎 が来るのは許さない、という意味です。
「否定先読み」という名前で呼ばれています。


指定した文字列は、存在してはならない
  • 山田・ベスト・昭雄 ※これは除外
  • 鈴木一郎
  • 佐藤梅子
  • 斎藤・エリザベス ※これも除外

^(?!.*ベス).+
先頭から調べていき ( ^ )、
~ベス が来るのは許さない、という意味です。

先頭の ^ がない場合、 の次の文字から選ばれてしまいます。

  • 山田・ベスト・昭雄
  • 斎藤・エリザベ

VBAで置換処理

このようなログがあるとします。

  • 開始日:2022年6月4日 土曜日
  • 完了日:2022年6月5日 日曜日

これを次のように置換処理したいと思います。

  • 開始=22/6/4
  • 終了=22/6/5

コードは以下のようになります。

Const s文字列 = "開始日:2022年6月4日 土曜日" & vbCrLf _
              & "終了日:2022年6月5日 日曜日"          '検索対象

Dim rx As New RegExp
rx.Patten = "^(開始|終了)日:\d\d(\d\d)年(\d+)月(\d+)日"    '検索パターン
rx.Global = True    '文字列全体を対象に検索するには、Trueにする

MsgBox rx.Replace(s文字列, "$1=$2/$3/$4")    'サブマッチ$1~4を置換処理に使う

rx.Pattern のからくりは以下のようになっています。

^(開始|終了)日:\d\d(\d\d)年(\d+)月(\d+)日開始日:2022年6月4日 土曜日

^(開始|終了)日:\d\d(\d\d)年(\d+)月(\d+)日開始日:20226月4日 土曜日

^(開始|終了)日:\d\d(\d\d)年(\d+)月(\d+)日開始日:2022年64 土曜日

rx.Replace(s文字列, "$1=$2/$3/$4") は、第1引数の文字列の合致部分を、第2引数で置き換えます。
第2引数には、$1 などが入っており、これは以下のようなルールで置き換えられます。

  • $1 ← 0番のサブマッチ(開始)
  • $2 ← 1番のサブマッチ(22)
  • $3 ← 2番のサブマッチ(6)
  • $4 ← 3番のサブマッチ(5)

したがって、$1=$2/$3/$4開始=22/6/5 に置換されます。

rx.Global = True は、「文章全体を対象に検索や置換処理を行う」という意味です。
デフォルトでは False です。これでは、最初に見つけた箇所しか置換処理をしてくれません。
2022年6月4日 だけではなく、2022年6月5日 のログも処理してほしいので True にします。


おわりに

正規表現で文字列処理をできるようになると、同様の処理を自分で実装したときに、コーディングの長さが何倍にもなることに気づくと思います。(したがってバグの数も……)
正規表現VBAだけでなく、他のあらゆる言語で使えます。使い勝手はちょっと違いますが、学んだことは色あせません。
どんどん活用していきましょう。