RegexクラスのReplaceメソッドで、正規表現にマッチする部分を置換したり削除したりする方法を解説。また、高度な正規表現の使い方も取り上げる。
ある文字列の中で、正規表現パターンに一致する全ての部分文字列を置換するには、Regexクラス(System.Text.RegularExpressions名前空間)のReplaceメソッドを利用する。本稿では、Replaceメソッドの使い方を解説するとともに、少し高度な正規表現の書き方も紹介する。
正規表現の基本的な書き方については、「.NET TIPS:正規表現を使って文字列がパターンに一致するか調べるには?[C#/VB]」と「.NET TIPS: 正規表現を使ってパターンに一致する全ての文字列を抽出するには?[C#/VB]」をご覧いただきたい。また、RegexクラスのReplaceメソッドは.NET Frameworkの初期からあるものだが、掲載したサンプルコードの全てを試すにはVisual Studio 2015以降が必要である。
RegexクラスのReplace静的メソッドを使えばよい(次のコード)。
なお、RegexクラスにはReplaceインスタンスメソッドもある。また、Replaceメソッドにオプション引数を渡す方法は、IsMatchメソッドやMatchesメソッドなどと同じである。静的メソッドとインスタンスメソッドの使い分けや、オプション引数を渡す方法については、「.NET TIPS:正規表現を使って文字列がパターンに一致するか調べるには?[C#/VB]」をご覧いただきたい。
using System.Text.RegularExpressions;
……省略……
string result
= Regex.Replace("{置換対象文字列}", "{正規表現パターン}", "{置換パターン}");
Imports System.Text.RegularExpressions
……省略……
Dim result As String _
= Regex.Replace("{置換対象文字列}", "{正規表現パターン}", "{置換パターン}")
上に示した書式だけを見ると、Stringクラス(System名前空間)のReplaceメソッドと同じように思える。だが、置換対象部分の指定に正規表現パターンが使えることに加えて、置換する文字列として「置換パターン」も指定できることで、柔軟で多彩な置換処理が可能になっているのだ。
まずは、正規表現パターンも置換パターンも使わないコンソールアプリの例を示そう(次のコード)。この場合は、StringクラスのReplaceメソッドと同じ結果が得られる。
using System.Text.RegularExpressions;
using static System.Console;
class Program
{
static void Main(string[] args)
{
// 「猫」を「子犬」に置換
// StringクラスのReplaceメソッド
WriteLine("吾輩は猫である。吾輩も猫である。".Replace("猫", "子犬"));
// 出力:吾輩は子犬である。吾輩も子犬である。
// RegexクラスのReplace静的メソッド
WriteLine(Regex.Replace("吾輩は猫である。吾輩も猫である。", "猫", "子犬"));
// 出力:吾輩は子犬である。吾輩も子犬である。
#if DEBUG
ReadKey();
#endif
}
}
Imports System.Text.RegularExpressions
Imports System.Console
Module Module1
Sub Main()
' 「猫」を「子犬」に置換
' StringクラスのReplaceメソッド
WriteLine("吾輩は猫である。吾輩も猫である。".Replace("猫", "子犬"))
' 出力:吾輩は子犬である。吾輩も子犬である。
' RegexクラスのReplace静的メソッド
WriteLine(Regex.Replace("吾輩は猫である。吾輩も猫である。", "猫", "子犬"))
' 出力:吾輩は子犬である。吾輩も子犬である。
#If DEBUG Then
ReadKey()
#End If
End Sub
End Module
上のコードでは冒頭で2つの名前空間をインポートしている。同じインポートが以降のサンプルコードでも必要であるが、以降では省略させていただく。
置換する文字列として空文字(=String.Empty)を与えればよい。
Replace静的メソッドをよく使うのは、正規表現パターンに一致した部分文字列を削除する処理だ。置換パターンとして空文字を与えれば、パターンに一致した部分を削除したことになるのである(次のコード)。
// パターンに一致した部分を削除する
// 「【」と「】」を削除する
WriteLine(Regex.Replace("吾輩は【猫】である。吾輩は【子犬】である。",
"[【】]", string.Empty));
// 出力:吾輩は猫である。吾輩は子犬である。
' パターンに一致した部分を削除する
' 「【」と「】」を削除する
WriteLine(Regex.Replace("吾輩は【猫】である。吾輩は【子犬】である。",
"[【】]", String.Empty))
' 出力:吾輩は猫である。吾輩は子犬である。
このような削除処理として、HTMLやXMLのデータからタグ部分を取り除いてテキストだけを残す処理がある。その方法を「.NET TIPS:正規表現を使って文字列から部分文字列を取り除くには?[C#、VB]」で詳しく解説しているので、ご覧いただきたい。
拡張メソッドを作るとよい。
置換処理を繰り返すとき、StringクラスのReplaceメソッドは連続したメソッドチェーンの形で書ける。RegexクラスのReplace静的メソッドではチェーンできないので、一時変数に格納してから次のReplace静的メソッドを書くことになる。しかし、次のコードのように拡張メソッドを作っておけば、すっきりメソッドチェーンとして書けるのである。
public static class RegexExtension
{
public static string RegexReplace(this string input,
string pattern, string replacement)
{
return Regex.Replace(input, pattern, replacement);
}
}
……省略……
// 置換の繰り返しの例:「猫」を「子犬」に変え、「【」と「】」を削る
// String.Replaceならメソッドチェーンで書ける
WriteLine("吾輩は【猫】である。吾輩も【猫】である。"
.Replace("猫", "子犬")
.Replace("【", string.Empty)
.Replace("】", string.Empty)
);
// 出力:吾輩は子犬である。吾輩も子犬である。
// Regexではチェーンできないので、一時変数への代入が必要
string work = Regex.Replace("吾輩は【猫】である。吾輩も【猫】である。",
"猫", "子犬");
string result = Regex.Replace(work, "[【】]", string.Empty);
WriteLine(result);
// 出力:吾輩は子犬である。吾輩も子犬である。
// でも、Regexだって拡張メソッドを書けばメソッドチェーンが可能
WriteLine("吾輩は【猫】である。吾輩も【猫】である。"
.RegexReplace("猫", "子犬")
.RegexReplace("[【】]", string.Empty)
);
// 出力:吾輩は子犬である。吾輩も子犬である。
Public Module RegexExtension
<System.Runtime.CompilerServices.Extension()>
Public Function RegexReplace(input As String, pattern As String,
replacement As String) As String
Return Regex.Replace(input, pattern, replacement)
End Function
End Module
……省略……
' 置換の繰り返しの例:「猫」を「子犬」に変え、「【」と「】」を削る
' String.Replaceならメソッドチェーンで書ける
WriteLine("吾輩は【猫】である。吾輩も【猫】である。" _
.Replace("猫", "子犬") _
.Replace("【", String.Empty) _
.Replace("】", String.Empty)
)
' 出力:吾輩は子犬である。吾輩も子犬である。
' Regexではチェーンできないので、一時変数への代入が必要
Dim work As String = Regex.Replace("吾輩は【猫】である。吾輩も【猫】である。",
"猫", "子犬")
Dim result As String = Regex.Replace(work, "[【】]", String.Empty)
WriteLine(result)
' 出力:吾輩は子犬である。吾輩も子犬である。
' でも、Regexだって拡張メソッドを書けばメソッドチェーンが可能
WriteLine("吾輩は【猫】である。吾輩も【猫】である。" _
.RegexReplace("猫", "子犬") _
.RegexReplace("[【】]", String.Empty)
)
' 出力:吾輩は子犬である。吾輩も子犬である。
置き換える文字として、見つけた文字列を使いたいことがある。例えば、パターンの前後にかっこを付けたいといった場合だ。
そのようなときに使えるのが、置換パターン内の置換要素だ。パターンに一致した部分文字列の全体は、「$0」または「$&」という記述で置換文字列に埋め込むことができる(次のコード)。
// 一致したパターンの前後にカギかっこを付ける
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。",
".+?。", "「$0」"));
// 出力:「吾輩は猫である。」「吾輩は子犬である。」
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。",
".+?。", "「$&」"));
// 出力:「吾輩は猫である。」「吾輩は子犬である。」
' 一致したパターンの前後にカギかっこを付ける
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。",
".+?。", "「$0」"))
' 出力:「吾輩は猫である。」「吾輩は子犬である。」
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。",
".+?。", "「$&」"))
' 出力:「吾輩は猫である。」「吾輩は子犬である。」
上の例で「$0」が一致したパターンの全体であった。では「$1」以降はというと、正規表現パターンで指定したグループを表すのだ。または、グループに付けた名前を番号の代わりに指定してもよい(次のコード)。
// キャプチャーしたグループを置換パターンで使う
WriteLine(Regex.Replace(
"www.foo.comとftp.foo.comは置換し、mail.foo.comは置換しない",
@"(www|ftp)\.foo\.", "$1.bar."));
// 出力:www.bar.comとftp.bar.comは置換し、mail.foo.comは置換しない
WriteLine(Regex.Replace(
"www.foo.comとftp.foo.comは置換し、mail.foo.comは置換しない",
@"(?<host>www|ftp)\.foo\.", "${host}.bar."));
// 出力:www.bar.comとftp.bar.comは置換し、mail.foo.comは置換しない
' キャプチャーしたグループを置換パターンで使う
WriteLine(Regex.Replace(
"www.foo.comとftp.foo.comは置換し、mail.foo.comは置換しない",
"(www|ftp)\.foo\.", "$1.bar."))
' 出力:www.bar.comとftp.bar.comは置換し、mail.foo.comは置換しない
WriteLine(Regex.Replace(
"www.foo.comとftp.foo.comは置換し、mail.foo.comは置換しない",
"(?<host>www|ftp)\.foo\.", "${host}.bar."))
' 出力:www.bar.comとftp.bar.comは置換し、mail.foo.comは置換しない
キャプチャーしたグループを参照することは、正規表現パターン内でも前方参照構成体としてサポートされている。同じ文字列が複数回現れることを表現できるのだ。例えば次のコードのようにすると、連続した文字を1文字に変換できる。
// 正規表現パターン内の前方参照「\N」/「\k<名前>」
WriteLine(Regex.Replace("吾輩は猫猫猫である。", @"(.)\1+", "$1"));
// 出力:吾輩は猫である。
WriteLine(Regex.Replace("吾輩は猫猫猫である。", @"(?<c>.)\k<c>+", "${c}"));
// 出力:吾輩は猫である。
' 正規表現パターン内の前方参照「\N」/「\k<名前>」
WriteLine(Regex.Replace("吾輩は猫猫猫である。", "(.)\1+", "$1"))
' 出力:吾輩は猫である。
WriteLine(Regex.Replace("吾輩は猫猫猫である。", "(?<c>.)\k<c>+", "${c}"))
' 出力:吾輩は猫である。
前回は否定先読みアサーションを紹介した。ここでは、その真偽値が反対になる肯定先読みアサーション「(?=)」と、さらに探索方向が逆になる肯定後読みアサーション「(?<=)」を解説する。
例題として、「(吾輩|私)は○○である」の「○○」だけを「人間」に置き換えるという処理を考えてみよう。
これは、置換パターンで$Nを使っても実現できる(次のコード)。
// 「(吾輩|私)は○○である」の「○○」だけを置き換える
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。私は鳥である。",
"(吾輩は|私は).+?(である)", "$1人間$2"));
// 出力:吾輩は人間である。吾輩は人間である。私は人間である。
' 「(吾輩|私)は○○である」の「○○」だけを置き換える
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。私は鳥である。",
"(吾輩は|私は).+?(である)", "$1人間$2"))
' 出力:吾輩は人間である。吾輩は人間である。私は人間である。
上の書き方で気になるのは、コードを読むときに「置換パターンの$1/$2に入るのは何か?」と考えなければならないところだ。処理の目的は「○○」を「人間」に置き換えるだけであるから、置換パターン$1/$2に何が入るかを考えるのは余計なことなのだ。
そこで、「○○」の部分だけをマッチングできないかと考えてみる。前方に「吾輩は|私は」があり、後方に「である」があるところが置換したい「○○」の部分だ。「○○」の部分のマッチングを計算している時点で考えると、そこから振り返って見れば「吾輩は|私は」があるはずだし(肯定後読みアサーション=positive lookbehind assertion)、そこから先を見れば「である」があるはずだ(肯定先読みアサーション=positive lookahead assertion)。「後読み」といわれてもちょっと分からないかもしれないが、「look behind」(振り返って見る)ことである。
肯定後読みアサーション「(?<=)」と肯定先読みアサーション「(?=)」で囲めば、「○○」の部分だけをマッチさせられる。そのことをまずMatchesメソッドを使って確認してみよう(次のコード)。
var results = Regex.Matches("吾輩は猫である。吾輩は子犬である。私は鳥である。",
"(?<=吾輩は|私は).+?(?=である)");
foreach (Match m in results)
WriteLine(m);
// 出力:
// 猫
// 子犬
// 鳥
Dim results = Regex.Matches("吾輩は猫である。吾輩は子犬である。私は鳥である。",
"(?<=吾輩は|私は).+?(?=である)")
For Each m In results
WriteLine(m)
Next
' 出力:
' 猫
' 子犬
' 鳥
肯定アサーションで囲めば置換したい「○○」の部分だけをマッチングできると分かったので、後はその部分をReplace静的メソッドで「人間」に置換するだけだ(次のコード)。
// 「(吾輩|私)は○○である」の「○○」だけを置き換える
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。私は鳥である。",
"(?<=吾輩は|私は).+?(?=である)", "人間"));
// 出力:吾輩は人間である。吾輩は人間である。私は人間である。
' 「(吾輩|私)は○○である」の「○○」だけを置き換える
WriteLine(Regex.Replace("吾輩は猫である。吾輩は子犬である。私は鳥である。",
"(?<=吾輩は|私は).+?(?=である)", "人間"))
' 出力:吾輩は人間である。吾輩は人間である。私は人間である。
正規表現を使って文字列中のパターンに一致する部分文字列を置換するには、RegexクラスのReplace静的メソッドを使えばよい(同じ処理を繰り返し実行すると分かっているなら、前々回のIsMatchメソッドと同様にコンパイルオプション付きでRegexクラスのインスタンスを作り、Replaceインスタンスメソッドを使う)。
マッチさせる正規表現パターンが複雑なときは、まずMatchesメソッド(あるいはMatchメソッド)を使って先に正規表現パターンの正しさを確認するとよい。
また、本稿では高度な正規表現の一部も簡単に紹介した。詳しくはMSDNの「.NET Framework の正規表現」をご覧いただきたい。
利用可能バージョン:.NET Framework 1.1以降(サンプルコードにはそれ以降の構文も含む)
カテゴリ:クラス・ライブラリ 処理対象:文字列
使用ライブラリ:Regexクラス(System.Text.RegularExpressions名前空間)
使用ライブラリ:Matchクラス(System.Text.RegularExpressions名前空間)
使用ライブラリ:MatchCollectionクラス(System.Text.RegularExpressions名前空間)
関連TIPS:正規表現を使ってパターンに一致する全ての文字列を抽出するには?[C#/VB]
関連TIPS:正規表現を使って文字列がパターンに一致するか調べるには?[C#/VB]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
関連TIPS:正規表現を使って部分文字列を取得するには?[C#、VB]
関連TIPS:正規表現のパターン内にコメント文を記述するには?[C#、VB]
関連TIPS:正規表現を使って文字列から部分文字列を取り除くには?[C#、VB]
Copyright© Digital Advantage Corp. All Rights Reserved.