一度コンパイルしてからマッチする
パターンはバッククォートの生文字列リテラルで書きます。普通のダブルクォートの文字列ではすべてのバックスラッシュを2つ重ねる必要があり("\\d+")、すぐに読めなくなります。
regexp.MustCompile はパターンが不正だとpanicします。ソースコードに書いたパターンにはそれが望ましい挙動で、打ち間違いは最初のリクエストではなく起動時にすぐ失敗します。ユーザーや設定から来るパターンには regexp.Compile を使い、エラーを処理します。
re, err := regexp.Compile(userPattern)
if err != nil {
return fmt.Errorf("bad pattern: %w", err)
}
コンパイルは一度だけ。 コンパイルはマッチングよりはるかに高くつきます。リクエストや行ごとに実行される関数の中の regexp.MustCompile は、毎回その処理を繰り返します。コンパイルした *regexp.Regexp は、パッケージレベルの var か構造体のフィールドに置きます。コンパイル済みの正規表現は、多くのゴルーチンから同時に使っても安全です。
regexp.MatchString(pattern, s) は1回の呼び出しでコンパイルとマッチを行います。一度きりのチェックには問題ありませんが、ループの中では無駄です。
一致を探す
メソッド名は、Find + All? + String? + Submatch? + Index? というパターンに従います。
| 部分 | 意味 |
|---|---|
All | 重ならないすべての一致。上限 n を受け取る(すべてなら -1) |
String | string に対して動く。これがないと、メソッドは []byte を受け取って返す |
Submatch | キャプチャグループも返す |
Index | テキストの代わりにバイトオフセットを返す |
FindString は一致がないと "" を返し、空文字列に一致した場合と区別できません。パターンが空のテキストに一致しうるなら、FindStringIndex(一致がなければ nil を返す)を使うか、先に MatchString を使います。All 版は何も一致しなければ nil を返します。
キャプチャグループ
かっこはキャプチャします。FindStringSubmatch はインデックス0に一致全体を、その後にグループごとに1つの要素を返します。
結果にインデックスでアクセスする前に、必ず nil をチェックします。一致がないと m[1] はpanicします。
(?:...) はキャプチャせずにグループ化し、選択や繰り返しに使います:(?:ab)+。名前付きグループは (?P<name>...) と書き、Go 1.22以降は (?<name>...) とも書けます。
置換する
グループ参照の典型的な罠があります。"$1x" は 1x という名前のグループとして読まれ、それは存在しないので空文字列に展開されます。"${1}x" と書きます。
固定の文字列には、strings.ReplaceAll、strings.Contains、strings.Split のほうが正規表現より単純で高速です。regexp を使うのは、探しているテキストが固定の値ではなく形を持つときです。
構文の早見表
GoはRE2の構文を使い、おなじみのPerl風の機能をカバーしています。
| 構文 | 一致するもの |
|---|---|
. | 改行以外の任意の文字((?s) では改行も) |
\d \w \s | 数字、単語文字 [0-9A-Za-z_]、空白(ASCIIのみ) |
\D \W \S | それぞれの否定 |
[abc] [^abc] [a-z] | 文字クラス |
\pL \p{Greek} | Unicodeのクラス:任意の文字、任意のギリシャ文字 |
* + ? {n,m} | 繰り返し、欲張り |
*? +? ?? | 繰り返し、控えめ |
^ $ | テキストの始まりと終わり((?m) では行の) |
\b | 単語の境界(ASCII) |
a|b | 選択 |
(?i) | その位置から囲むグループの終わりまで大文字小文字を区別しない。たいていパターンの先頭に書く |
\d と \w はASCIIにしか一致しません。「どの言語の文字でも」なら \pL を、Unicodeの任意の数字なら \p{Nd} を使います。日本語のテキストを扱うときは、\p{Han}、\p{Hiragana}、\p{Katakana} も使えます。regexp.QuoteMeta(s) は s の特殊文字をすべてエスケープするので、ユーザーの入力からパターンを組み立てるときに必要です。
RE2でできないこと
Goのregexpは、マッチングが入力の長さに比例した時間で済むことを保証します。その保証を壊す機能はサポートされていません。
- 先読みも後読みもない:
(?=...)、(?!...)、(?<=...)、(?<!...)はコンパイルエラーです。Go 1.22で名前付きグループに(?<name>...)が使えるようになったため、後読みは以下のプログラムが示すように、invalid named captureというわかりにくいメッセージで失敗するようになりました。 - 後方参照がない: 同じ文字の連続に一致させる
(\w)\1は書けません。 - 独占的量指定子もアトミックグループもない。
回避策はほぼ常に同じです。必要なものより少し多めにキャプチャし、Goでフィルタしたり切り出したりします。その見返りとして、Goの正規表現は壊滅的なバックトラックに追い込まれることがありません。短い入力でPCREのエンジンを固まらせうる (a+)+$ のようなパターンも、ここでは線形時間で動きます。パターンや入力が外部から来るときに、これが重要になります。
よくある間違い
- ループやハンドラの中でコンパイルする。 パッケージレベルの変数に一度だけコンパイルします。
- ダブルクォートのパターン。
"\d"はそもそも正しいGoの文字列ではありません。バッククォートを使います。 - 検証でアンカーを忘れる。
\d{5}は"abc123456xyz"の中に一致します。^\d{5}$を使います。 nilチェックなしにサブマッチの結果にインデックスでアクセスする。\wや\bがASCII以外のテキストを理解すると期待する。\pLのようなUnicodeのクラスを使います。- 固定の文字列に正規表現を使う。
stringsパッケージのほうが明快で高速です。
よくある質問
Goで文字列が正規表現に一致するか確認するには?
バックスラッシュをエスケープしなくて済むよう、パターンをバッククォートの生文字列で書いて regexp.MustCompile で一度だけコンパイルし、re.MatchString(s) を呼びます。アンカー(^ と $)がなければ、MatchString はパターンが文字列のどこかに一致すればtrueを返します。
Goのregexpは先読みや後読みをサポートしていますか?
していません。Goの regexp はRE2の構文を使い、先読み、後読み、後方参照がありません。その代わり、マッチングは入力の長さに比例した時間で動くので、悪意のあるパターンや入力でプログラムが固まることはありません。代わりにキャプチャグループと少しのGoのコードを使うか、チェックを2つの正規表現に分けます。
regexp.Compileとregexp.MustCompileの違いは何ですか?
Compile はコンパイルしたパターンとエラーを返します。MustCompile は代わりに、不正なパターンでpanicします。ソースコードに書いたパターンには MustCompile を使い、たいていはパッケージレベルの変数に入れて、打ち間違いが起動時に失敗するようにします。ユーザーや設定ファイルから来るパターンには Compile を使います。
Goで正規表現のキャプチャグループを取得するには?
FindStringSubmatch を使います。インデックス0が一致全体、インデックス1、2、…がグループのスライスを返し、一致しなければ nil を返します。すべての一致には FindAllStringSubmatch(s, -1) を使います。名前付きグループ (?P<year>\d{4}) は re.SubexpIndex("year") で探せます。