C#の正規表現の使い方をまとめます。
パターンの記述方法についてはよくまとまっている記事がいくらでもあるのでそれ以外の情報についてまとめます。
基本的な使い方
IsMatch, Match, Matches
文字列が正規表現にマッチしているかどうかを調べるにはRegex.IsMatch()
を使います。
var regex = new Regex(@"^https://.+\.com"); regex.IsMatch("https://google.com"); // true
正規表現にマッチしている部分を取得するにはRegex.Match()
を使います。
var regex = new Regex(@"^https://.+\.com"); var match = regex.Match("https://google.com/testtest"); match.Success; // true (マッチしたかどうか) match.Value; // https://google.com(マッチした部分の文字列)
正規表現にマッチしている部分をいくつか取得するにはRegex.Matches()
を使います。
var regex = new Regex(@"test"); var matches = regex.Matches("https://google.com/testtest"); matches.Count; // 2(マッチした個数) matches[0].Value; // test(1個目のマッチした値)
最短マッチを表す「?」
いま、ある文字列の中から [ と ] で囲まれている部分を全て抜き出したいとします。
これを正規表現で書いてみます。
var regex = new Regex(@"\[.*\]"); var matches = regex.Matches("[test1][test2]"); matches[0].Value; // [test1][test2]
上記はうまくいくように思えますが、実際に実行してみると [test1][test2] という結果が得られます。
これは正規表現による条件を満たす部分のうち、最長の部分がマッチするという仕様によるものです。
以下のように*の後に?を付けることで、最短の部分がマッチするようになります。
var regex = new Regex(@"\[.*?\]"); // ?を付ける var matches = regex.Matches("[test1][test2]"); matches[0].Value; // [test1] matches[1].Value; // [test2]
オプションまとめ
正規表現クラス生成時にはオプションを指定することができます。
new Regex(@"\[.*?\]", RegexOptions.IgnoreCase | RegexOptions.Singleline);
以下、各オプションの意味についてまとめます。
名前 | 説明 |
---|---|
IgnoreCase | 検索時に大文字と小文字を区別しない |
Multiline | ^と$の意味を変更し、各行の先頭と末尾にもそれらがマッチするようにする |
ExplicitCapture | 名前か番号を指定したグループだけを有効なキャプチャであるとみなす |
Compiled | 正規表現をコンパイルしてアセンブリを作成する 実行速度は速くなるが起動時間は長くなる |
Singleline | .の意味を変更し\nにも一致するようにする |
IgnorePatternWhitespace | 正規表現を複数行に分割して行末に#を使ったコメントを入れられるようになる |
RightToLeft | 検索が右から左になされるようになる |
ECMAScript | ECMAScript準拠の動作を有効にする |
CultureInvariant | 言語の違いが無視される |
置換
Regex.Replace()
を使うと正規表現にマッチした部分を置換できます。
new Regex(@"<date>") .Replace("Today is <date>.", DateTime.Now.ToShortDateString()); // Today is 2020/09/22.
staticメソッドも用意されています。
Regex.Replace("Today is <date>.", "<date>", DateTime.Now.ToShortDateString();
分割
Regex.Split()
を使うと正規表現にマッチした部分を区切り文字として対象の文字列を分割できます。
new Regex(",").Split("98127,432,25,870,4214,234"); // ["98127", "432", "25", "870", "4214", "234"]
正規表現にグループを使うと区切り文字も含めた分割結果が得られます。
new Regex("(,)").Split("98127,432,25,870,4214,234"); // ["98127", ",", "432", ",", "25", ",", "870", ",", "4214", ",", "234"]
またstaticメソッドも用意されています。
Regex.Split("98127,432,25,870,4214,234", ",");
グループ
正規表現を()で囲むとグループを指定できます。
グループにはいくつかの使い方があるので以下にまとめます。
OR条件を作る
グループ内の文字列を | で区切ることでOR条件を指定できます。
例えば以下のように書くとSeptember、November、Decemberのいずれかであればマッチするといった条件が作れます。
var regex = new Regex(@"(Sept|Nov|Dec)ember"); var matches = regex.Matches("January February March April May June July August September November December"); matches[0].Captures[0].Value; // September matches[1].Captures[0].Value; // November matches[2].Captures[0].Value; // December
複数のグループを取り扱う
複数のグループを作ることもできます。
この場合にはMatch.Groups
を使って各グループの値を取り出します。
var regex = new Regex(@"(\d+)/(\d+)/(\d+)"); var match = regex.Match("2020/09/22"); match.Groups[0].Captures[0].Value; // 2020/09/22 ※Groups[0]にはマッチした部分全体の文字列が入る match.Groups[1].Captures[0].Value; // 2020 match.Groups[2].Captures[0].Value; // 09 match.Groups[3].Captures[0].Value; // 22
またグループに ?:
を付けるとそのグループはキャプチャされません。
var regex = new Regex(@"(\d+)/(?:\d+)/(\d+)"); // 二個目のグループに?:を付けた var match = regex.Match("2020/09/22"); match.Groups[0].Captures[0].Value; // 2020/09/22 match.Groups[1].Captures[0].Value; // 2020 match.Groups[2].Captures[0].Value; // 22
名前付きグループ
グループに名前を付けることもできます。
var regex = new Regex(@"(?<year>\d+)/(?<month>\d+)/(?<day>\d+)"); var match = regex.Match("2020/09/22"); match.Groups["year"].Captures[0].Value; // 2020 match.Groups["month"].Captures[0].Value; // 09 match.Groups["day"].Captures[0].Value; // 22
前方参照コンストラクト
前方参照コンストラクトを使うと以下のようにキャプチャしたグループの位置を使って正規表現を作ることができます。
var regex = new Regex(@"(\w{3})\1"); // 3文字が2回繰り返している部分にマッチ var matches = regex.Matches("atwjamiamijqkfaqfaqfawekaekapkpewjfak"); matches[0].Value; // amiami matches[1].Value; // faqfaq matches[2].Value; // ekaeka
また以下のように\k<グループ名>
とすれば名前付きグループを名前で指定できます。
var regex = new Regex(@"(?<source>\w{3})\k<source>"); var matches = regex.Matches("atwjamiamijqkfaqfaqfawekaekapkpewjfak"); matches[0].Value; // amiami matches[1].Value; // faqfaq matches[2].Value; // ekaeka
Capturesが複数になるケース
上記ではグループのCaptures[0]
のみ使用していました。
以下のように一つのグループが複数回マッチするようなケースではCapturesの他のインデックスを指定する必要があります。
var regex = new Regex(@"(\d+/*)+"); var match = regex.Match("2020/09/22"); match.Groups[1].Captures[0].Value; // 2020/ match.Groups[1].Captures[1].Value; // 09/ match.Groups[1].Captures[2].Value; // 22
先読み、後読みアサーション
ゼロ幅の肯定先読みアサーション
(?= subexpression )
という書式でゼロ幅の肯定先読みアサーションを使うと、
後ろに指定したパターンが現れる部分とマッチさせることができます。
またこのときsubexpression
はマッチ結果には含まれません。
var regex = new Regex(@"(?<name>\w+):(?<count>\d+)(?=個)"); var matches = regex.Matches("りんご:2個 みかん:3個 なし:5個"); $"{matches[0].Groups["name"].Value}: {matches[0].Groups["count"].Value}"; // りんご: 2 $"{matches[1].Groups["name"].Value}: {matches[1].Groups["count"].Value}"; // みかん: 3 $"{matches[2].Groups["name"].Value}: {matches[2].Groups["count"].Value}"; // なし: 5
ゼロ幅の否定先読みアサーション
(?! subexpression )
という書式でゼロ幅の肯定先読みアサーションを使うと、
後ろに指定したパターンが現れない部分とマッチさせることができます。
またこのときsubexpression
はマッチ結果には含まれません。
前節の肯定先読みアサーションの反対です。
ゼロ幅の肯定後読みアサーション
(?<= subexpression )
という書式でゼロ幅の肯定後読みアサーションを使うと、
前に指定したパターンが現れる部分とマッチさせることができます。
またこのときsubexpression
はマッチ結果には含まれません。
var regex = new Regex(@"(?<=-)(?<value>\d+)"); var matches = regex.Matches("-123 9028 -191 12 -1324 -158 51"); $"{matches[0].Groups["value"].Value}"; // 123 $"{matches[1].Groups["value"].Value}"; // 191 $"{matches[2].Groups["value"].Value}"; // 1324 $"{matches[3].Groups["value"].Value}"; // 158
ゼロ幅の否定後読みアサーション
(?<! subexpression )
という書式でゼロ幅の否定後読みアサーションを使うと、
前に指定したパターンが現れない部分とマッチさせることができます。
またこのときsubexpression
はマッチ結果には含まれません。
前節の肯定後読みアサーションの反対です。