Step by step 正規表現入門
はじめに
最近ではスクリプト系の言語はもちろん C++ 以降の言語ではほとんど正規表現が標準機能として使えるようになっています。 C++ でも C11 の新規約では正規表現が標準ライブラリとなりました。今後、正規表現はプログラミングで当たり前のように使われる流れにあると言えるでしょう。
何故正規表現がそんなに使われるのでしょうか?
それは正規表現を使えば、圧倒的に文字列処理が簡単にかけるからです。
また、プログラミング言語で使えるだけでなく、 高機能なエディターでは正規表現による検索と置換が行えます。 これはコードリーディングやコーディングを楽にしてくれます。
プログラミングをするのならば、正規表現を知らないのはもったいないです。
ただ、正規表現を覚えようとした場合、 ルールもたくさんありますし、 言語やエディターの説明では基本的なものからめったに使わないものまで まとめて説明されがちです。 そこで、 Step by Step で少しずつ覚えていけるような正規表現の入門を書いてみました。
また、実際に使いながらの方が覚えやすいので、 以下の主要なエディターでの使い方も説明しています。
- Emacs
- Visual Studio
- K2Editor
- 秀丸エディター
対象読者
プログラマー向けの内容になっています。ただ、プログラミングの知識がないと理解できない ということはないんじゃないかなと思います。
進め方
進め方は基本的に最初から順番に始めてください。ただし、途中でやめても、そこそこ役に立つ構成にしています。 途中でやめたとしても、それまでの知識で使っていれば、 次の情報も知りたいとなるかもしれません。 その時また続きを読んでください。
さっと知りたいという場合はまとめページを見てください。 細かい説明を省いて、必要な情報を簡単にまとめています。
また、正規表現はだいたい知っているから、エディターでの使い方を知りたいという人は以下のページを見てください。
Step by step 正規表現入門 - 正規表現を使ったエディターでの置換
実際のコーディングで使うような例で説明した方が良さが伝わりやすいと思って、 C++ のサンプルを考えてみました。
置換のサンプル
まず、次のような列挙型の定義があったとします。enum FooType { FooA, FooB, FooC };こういった列挙型を使った switch 文を書くことはよくあります。例として列挙型を文字列でダンプ表示するための関数を作ります。 これを作るとき、列挙型定義をコピペして、正規表現で置換するとすぐに作れるようになります。
-
まず、ダンプ用関数の雛形を用意します。
std::ostream &operator<<(std::ostream &out, FooType val) { switch (val) { default: out << (static_cast<int>(val)) << " (Not FooType)"; } return out; }
-
列挙型定義の部分を貼り付けます。
std::ostream &operator<<(std::ostream &out, FooType val) { switch (val) { FooA, FooB, FooC default: out << (static_cast<int>(val)) << " (Not FooType)"; } return out; }
- 貼り付けた部分を置換します。
-
以下のような定義になって完成です。
std::ostream &operator<<(std::ostream &out, FooType val) { switch (val) { case FooA: out << "FooA"; break; case FooB: out << "FooB"; break; case FooC: out << "FooC"; break; default: out << (static_cast<int>(val)) << " (Not FooType)"; } return out; }
正規表現 | ([a-zA-Z_0-9]+),? |
---|---|
置換後文字列 | case \1: out << "\1"; break; |
ここでのグルーピングは前回の複数指定とは関係なく、取り出す文字列の指定のためだけのものです。
この () によるマッチ文字の取得はプログラミング時にもよく使われます。
() はパターンの複数指定よりもむしろマッチ文字の取得としての用途の方が重要だと思います。
置換後の文字列でマッチした文字列を取り出すには以下のメタ文字を使います。
\番号番号は () の出てきた順で、 \1, \2, ... といった感じで記述します。 この例では 1 個しか出てきていないので、 \1 です。
\0 とした場合はパターン全体でマッチした部分(FooA, など)となります。
エディターの置換機能の使い方
実際の置換を各エディターでやって行きましょう。Emacs
Emacs では M-% で逐次置換(query-replace)になります。 正規表現による置換のコマンドは C-M-% (ESC Ctrl+Shift+5) または M-x query-replace-regrep で実行します。コマンドを実行すると正規表現、置換後文字列を聞かれるので、それぞれ入力します。
このとき、前回説明したように Emacs では () の前に \ を付ける必要がある点に注意してください。
Query replace regrep: | \([a-zA-Z_0-9]+\),? |
---|---|
with: | case \1: out << "\1"; break; |

入力後は通常の逐次置換と同様の操作で置換していくことができます。
よく使うキーは次のものです。詳しくは ? でヘルプを見てください。
キー | 機能 |
---|---|
y または スペース | 置換実行(1 つ) |
n | スキップ |
! | 残りすべての置換を実行 |
^ | 前のマッチ位置に戻る |
q または Return | 中止 |
英語キーボードならいいのでしょうが、 C-M-% はかなり押しづらいです。 以下のような感じで割り当てを .emacs.el に書いておくといいでしょう。
(global-set-key "\M-5" 'query-replace-regexp)
Visual Studio
[編集] → [検索と置換] → [クイック置換] (Ctrl+H)などで置換ダイアログを呼び出します。検索のときと同様にオプションで正規表現にチェックを入れておきます。
Visual Stodio の場合は置換用のグルーピングは () ではなく、 {} を使います。
また、 ? は使えないので、 * で代用します。

K2Editor
[検索] → [置換] (Ctrl-R) などで置換ダイアログを呼び出します。検索のときと同様に正規表現にチェックを入れておきます。

連続して置換していくので、 [連続置換] の方のボタンをクリックして、置換していきます。
秀丸エディター
[検索] → [置換] (Ctrl-R) などで置換ダイアログを呼び出します。検索のときと同様に正規表現にチェックを入れておきます。

連続してするには、 [置換の前に確認] にチェックを入れて、 [全置換] をクリックします。
なお、秀丸はカーソル位置からの置換はできないので、サンプルはもっと絞ったパターンにしています。
欲張りマッチとものぐさマッチ
以下のような文章で強調文字を取得するパターン <em>(.*)</em> を使ったとします。<em>正規表現</em>は<em>便利</em>ですこの時マッチするのは "正規表現</em>は<em>便利" の文字です。
正規表現ではこのようになるべく長い文字列がマッチすることを欲張りマッチまたは最大マッチといい、 なるべく短くマッチすることをものぐさマッチまたは最小マッチといいます。
正規表現では通常は欲張りマッチとなっています。
欲張りマッチで余分なマッチをしてしまうことはよくありますが、 たいていパターンを工夫すれば解決できるので、入門としてはこれ以上詳しくは説明しません。
ただ、エディターによってはオプションで簡単に切り替えられるので、 用語としては覚えておくといいでしょう。
次は ?
置換を覚えて頻繁に書いていると空白を指定する [ \t] や変数用の [a-zA-Z0-9_] などを結構使います。 これを何度も書いていると結構めんどくさくなると思います。次はこれを楽に書く方法を紹介しましょう。
Emacs の検索、置換における大文字小文字の区別の切り替え
Emacs にはいろんな検索、置換機能があります。 その際、大文字小文字を区別するしないを設定で変えることができます。 今回はその設定方法について紹介したいと思います。
検索
検索時に大文字小文字を区別しないようにする設定です。~/.emacs.d/init.el に次のような記述をします。
;; 検索(全般)時には大文字小文字の区別をしない (setq case-fold-search t) ;; インクリメンタルサーチ時には大文字小文字の区別をしない (setq isearch-case-fold-search t)case-fold-search が検索全般に対して使われる変数で、 isearch-case-fold-search がインクリメンタルサーチ(C-s)時の変数です。
nil で区別し、 t(nil 以外) で区別しなくなります。
M-x customize-variable などでカスタマイズの機能を使って設定することもできます。
バッファー、ファイル名補完
バッファーの切り替え(C-x b) などでバッファー名を指定する場合やファイルパスを指定する場合 [TAB] で補完できます。その際に大文字小文字を区別しない設定です。;; バッファー名の問い合わせで大文字小文字の区別をしない (setq read-buffer-completion-ignore-case t) ;; ファイル名の問い合わせで大文字小文字の区別をしない (setq read-file-name-completion-ignore-case t)ファイルパス指定(read-file-name-completion-ignore-case)は Windows の場合、 デフォルトで t です。
バッファー指定(read-buffer-completion-ignore-case)のデフォルトは nil で挙動が違うので、 t にしておいた方が使いやすいと思います。
置換 query-replace
置換時の大文字小文字の区別の切り替えは case-replace 変数を使います。;;置換時に大文字、小文字をそのまま (setq case-replace nil)この変数が t または nil で変換後の大文字小文字が変わります。 query-replace (M-%) を使って、 foo を bar に置換した場合は次のようになります。
変換前 | 変換後 t | 変換後 nil |
---|---|---|
foo | bar | bar |
Foo | Bar | bar |
FOO | BAR | bar |
dabbrev 補完
M-/ で呼び出される dabbrev-expand というコマンドがあります。 これは単語を途中まで書いているとき、 開いているバッファー内からマッチする単語を逐次補完してくれる機能です。"FooBarClass" という単語があったとして、 コマンドを実行すると置換用の変数(case-replace) によって、補完結果が変わります。
foob[M-/] => foobarclass ( t ) => FooBarClass ( nil )この場合は case-replace の値は nil の方がいいでしょう。
なお、これは case-fold-search が t の場合です。 nil の場合はそもそも "FooBarClass" が "foob" の候補になりません。
この dabbrev の時だけ動作を変える変数が用意されています。
- dabbrev-case-fold-search
- dabbrev-case-replace
こちらは M-x customize-customize-group [RET] dabbrev などのカスタマイズで設定した方が簡単でしょう。
ちなみに dabbrev-expand は逐次補完ですが、 候補を表示する dabbrev-completion (M-C-/) もあります。
特定のモードだけ変更
特定のモードだけ大文字小文字の挙動を変更したい場合があります。例えば、 先ほどの dabbrev-expand ではコーディング時は大文字小文字を保持(nil) した方が使いやすいですが、 メッセージファイルやテキストファイルなどで英語の文章を書いている場合は、 t の方が使いやすいと思います。
各モードの hook と変数をバッファーで固有にする機能(make-local-variable) を使えば、特定のモードだけ挙動を変えることができます。
以下はテキストモードだけ値を変える例です。
;; テキストモードでは dabbrev で大文字小文字を保持しない (add-hook 'text-mode-hook '(lambda () (set (make-local-variable 'dabbrev-case-replace) t)))
特定のコマンドだけ変更
dabbrev では変数が用意されていましたが、 用意されていないコマンドなどのために特定のコマンドの時だけ変えることもできます。以下は query-replace (M-%) による置換だけ case-replace を t にする設定です。
;; 置換時に大文字、小文字をそのままで (setq case-replace nil) ;; でも query-replace の時は変化させる (defadvice query-replace (around replace-ajust activate compile) "query-replace の大文字小文字検索の調整" (let ((case-replace t)) ad-do-it))なお、 TAGS ファイルを使った置換(M-x tags-query-replace) も内部で query-replace が呼ばれるため、 この設定で OK です。