正規表現とは検索や置換で指定する文字列をパターン表現する方法です。プログラミング言語やテキストエディタなどで利用できます。もちろんC#でも使えるし覚えておくと非常に便利です。
与えられた文字列のなかに半角数字が3つ以上連続する部分を取り出すのであれば以下のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using System; using System.Text.RegularExpressions; class Program { public static void Main() { string str = "abc1234xyz56789w"; Regex regex = new Regex(@"\d{3,}"); MatchCollection matches = regex.Matches(str); foreach (Match match in matches) Console.WriteLine(match.Value); } } |
Contents
.は任意の1文字を表す .
.は任意の1文字を表します。また後述しますが.*+?^$|(){}[]\には特別な意味があるのでこれらを検索するときは前に\をつけます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System; using System.Text.RegularExpressions; class Program { public static void Main() { // 文字列のなかに a の後に任意の文字が2回続きその後にbが続くものがあるか? string str = "qacdbcaxybd"; Regex regex = new Regex(@"a..b"); Match match = regex.Match(str); if (match.Success) { // 見つかった場合 Console.WriteLine($"先頭から{match.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match.Value}】"); } else { // 見つからない場合 Console.WriteLine("マッチしない"); } MatchCollection matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } if (matches.Count == 0) Console.WriteLine("マッチしない"); } } |
条件をみたす1文字にマッチ
[]があるとそのなかに含まれる任意の1文字にマッチし、[0-5]だと012345のなかの1文字にマッチします。[^]だとそのなかに含まれない任意の文字にマッチします。[]内では\]^-には特別な意味があるのでこれらの文字を検索するときはそのまえに\が必要です。
例) .jpg または .png で終わる、半角スペースを含まない文字列なら [^ ]+(\.jpg|\.png (.を検索するので\が必要)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Program { public static void Main() { string str = "鳩と烏と鴨と鷺は別の生き物です"; Regex regex1 = new Regex(@"[鳩烏鴨]と"); // 「鳩と」「烏と」「鴨と」にマッチする MatchCollection matches1 = regex1.Matches(str); foreach (Match match0 in matches1) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine(""); Regex regex2 = new Regex(@"[^鳩烏]と"); // 「鴨と」にマッチする MatchCollection matches2 = regex2.Matches(str); foreach (Match match0 in matches2) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } } } |
直前の文字が指定された回数繰り返される
直前の文字が0回または1回出現することを表す ?
直前の文字が0回以上繰り返すことを表す *
直前の文字が1回以上繰り返すことを表す +
直前の文字が指定された回数(この場合は3回)繰り返すことを表す {3}
直前の文字が指定された回数以上(この場合は3回以上)繰り返すことを表す {,3}
直前の文字が指定された範囲の回数(この場合は3回以上5回以下)繰り返すことを表す {3,5}
例) $の後に3桁以上5桁以下の数字が続くのであれば、\$[0-9]{3,5} ($を検索するのであれば前に\が必要)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
class Program { public static void Main() { string str = "御御御付はありますが、御御付とか御御御御付とか御御御御御付はありません。三食昼寝付き"; Regex regex; MatchCollection matches; Console.WriteLine("御?付"); regex = new Regex(@"御?付"); // 「付」「御付」があればマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine("御*付"); regex = new Regex(@"御*付"); // 「付」「御付」「御…御付」があればマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine("御+付"); regex = new Regex(@"御+付"); // 「御付」「御…御付」があればマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine("御{3}付"); regex = new Regex(@"御{3}付"); // 「御御御付」があればマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine("御{3,}付"); regex = new Regex(@"御{3,}付"); // 「御御御付」「御御御御付」「御御御御御付」があればマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine("御{1,3}付"); regex = new Regex(@"御{1,3}付"); // 「御付」「御御付」「御御御付」があればマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } } } |
() で囲まれた文字列は文字列のまとまりを表す
() で囲まれた文字列は、文字列のまとまりを表します。 | と組み合わせることでさまざまな OR 検索を表現することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Program { public static void Main() { string str = "鳩とカラスと鴨と鷺は別の生き物です"; Regex regex1 = new Regex(@"(鳩|カラス|鴨)と"); // 「鳩と」「カラスと」「鴨と」にマッチする MatchCollection matches1 = regex1.Matches(str); foreach (Match match0 in matches1) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } } } |
行頭と行末
^ は、行頭を表し、$ は行末を表します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Program { public static void Main() { string str = "鳩でもわかるC#へようこそ"; Regex regex; Match match; MatchCollection matches; Console.WriteLine(@"^.+C"); regex = new Regex(@"^.+C"); // 「鳩でもわかるC」にマッチする match = regex.Match(str); if (match.Success) Console.WriteLine($"{match.Value}"); else Console.WriteLine($"マッチしない"); Console.WriteLine(@"C.+$"); regex = new Regex(@"C.+$"); // 「C#へようこそ」にマッチする match = regex.Match(str); if (match.Success) Console.WriteLine($"{match.Value}"); else Console.WriteLine($"マッチしない"); } } |
他に以下のようなものを覚えておくと便利です。
\d 半角数字1文字([0-9]と同じ)
\D 半角数字以外の1文字
\s 空白文字 (スペース, タブ, 改行) 1文字
\S 空白文字以外の1文字
\w 半角英数字記号1文字
\W 半角英数字記号以外の1文字
\n 改行文字
\t タブ文字
\A 文字列の先頭
\Z 文字列の末尾
先読みと後読み
先読みとはある文字列の後に特定の文字列が存在するかどうかを調べる機能です。
肯定先読みは、正規表現の中で文字列を (?= と ) で囲みます。\d+(?=円) とすれば 100円 という文字列の 100 の部分にマッチしますが、100ドル や 100ユーロ などの 100 の部分にはマッチしません。肯定先読みは、後ろに指定した文字列が続くという条件を課しつつ、その部分はマッチさせないというようなことを実現できます。
また逆に(?! と ) で囲めば否定先読みになります。\d+(?!円)とすれば 100ドル や 100ユーロ などの 100 の部分にはマッチしますが、100円 という文字列の 100 の部分にはマッチしなくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class Program { public static void Main() { string str = "100円と200ドルと300ユーロでは価値がぜんぜん違います"; Regex regex; Match match; MatchCollection matches; Console.WriteLine(@"\d+(?=円)"); regex = new Regex(@"\d+(?=円)"); // 「100円」の100にマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine(@"\d+(?!円)"); regex = new Regex(@"\d+(?!円)"); // 「100」の10と「200ドル」の200と「300ユーロ」の300にマッチする matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } } } |
後読みとはある文字列の前に特定の文字列が存在するかどうかを調べる機能です。
肯定後読みをすることで、指定した文字列が前に存在しないものを除外することができます。肯定後読みは、正規表現の中で文字列を (?<= と ) で囲みます。 (?<=台風)\d+ という正規表現は、台風1号 という文字列の 1 の部分にはマッチしますが、ひかり519号 という文字列や のぞみ237号 といった文字列の半角数字の部分にはマッチしません。 また逆に(?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class Program { static void Main() { string str = "台風1号でひかり519号とのぞみ237号が運休"; Regex regex; Match match; MatchCollection matches; Console.WriteLine(@"(?<=台風)\d+"); regex = new Regex(@"(?<=台風)\d+"); matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } Console.WriteLine(@"(?<!台風)\d+"); regex = new Regex(@"(?<!台風)\d+"); matches = regex.Matches(str); foreach (Match match0 in matches) { Console.WriteLine($"先頭から{match0.Index}番目にマッチ"); Console.WriteLine($"マッチした文字列は【{match0.Value}】"); } } } |
文字列の置換
正規表現で文字列の置換するときは以下のようにします。
半角数字4つの後に-が続き、その後に半角数字が2つ、さらに-が続き、その後に半角数字が2つ続くとき(例:今日は2023-12-07です)のとき、最初の-を年、次の-を月に置換して最後に日をつける場合。
() で囲まれた部分はグループと呼ばれ、左から順番に $1, \$, … という形で置換後の正規表現で参照することができるのです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Program { static void Main() { string str = "今日は2023-12-07です。明日は2023-12-08です。"; Regex regex = new Regex(@"(\d{4})-(\d{2})-(\d{2})"); Console.WriteLine(regex.Replace(str, "$1年$2月$3日")); // マッチした部分を固定された文字列に置換するならこれでOK Console.WriteLine(regex.Replace(str, "固定された文字列")); // マッチした部分を削除するならこれでOK Console.WriteLine(regex.Replace(str, "")); } } |
文字列を分割
2023年12月07日を文字列 年、月、日で分割します。
1 2 3 4 5 6 7 8 9 10 11 |
public class Program { static void Main() { string str = "2023年12月07日"; Regex regex = new Regex(@"[年月日]"); string[] strings = regex.Split(str); foreach (string s in strings) Console.WriteLine(s); } } |
最短一致
最短一致とは、繰り返しに使われる ? + * {n,m} の後に ? を付けることで、できるだけ短い文字列にマッチするようになる機能です。
以下の文字列のなかから /* と */ とその間にある文字列を削除する処理として以下のように書いたとします。
int a = 1; /* 変数 a に 1 を代入 */ int b = 2; /* 変数 b に 2 を代入 */ return a + b; /* a と b の合計を返す */
1 2 3 4 5 6 7 8 9 |
class Program { public static void Main() { string str = "int a = 1; /* 変数 a に 1 を代入 */ int b = 2; /* 変数 b に 2 を代入 */ return a + b; /* a と b の合計を返す */"; Regex regex = new Regex(@"/\*.*\*/"); Console.WriteLine(regex.Replace(str, "")); } } |
実行結果は int a = 1;だけになります。こうなってしまうのは「*」「+」などを使った正規表現などの場合、マッチする部分が複数ある場合「最も左から、最も長く一致するものにマッチする」からです。コメントを削除するのであれば最短一致を使わなければなりません。
1 2 3 4 5 6 7 8 9 |
class Program { public static void Main() { string str = "int a = 1; /* 変数 a に 1 を代入 */ int b = 2; /* 変数 b に 2 を代入 */ return a + b; /* a と b の合計を返す */"; Regex regex = new Regex(@"/\*.*?\*/"); // /\*.*\*/ ではなく /\*.*?\*/ にすればよい Console.WriteLine(regex.Replace(str, "")); } } |
では、よき正規表現ライフを。