正規表現を使って文字列を分割するには?[C#/VB]:.NET TIPS
RegexクラスのSplitメソッドを使用して、正規表現にマッチする部分をセパレーターとして、文字列を分割する方法を解説する。
正規表現パターンに一致する文字列をセパレーター(区切り)として使って文字列を分割するには、Regexクラス(System.Text.RegularExpressions名前空間)のSplitメソッドを利用する。本稿では、Splitメソッドの使い方を解説するとともに、少し高度な正規表現の書き方も紹介する。
RegexクラスのSplitメソッドは.NET Frameworkの初期からあるものだが、掲載したサンプルコードの全てを試すにはVisual Studio 2015以降が必要である。
なお、正規表現の書き方については、以下の.NET TIPSをご覧いただきたい。
正規表現を使って文字列を分割するには?
RegexクラスのSplit静的メソッドを使えばよい(次のコード)。
なお、RegexクラスにはSplitインスタンスメソッドもある。また、Splitメソッドにオプション引数を渡す方法は、IsMatchメソッドなどと同じである。静的メソッドとインスタンスメソッドの使い分けや、オプション引数を渡す方法については、「.NET TIPS:正規表現を使って文字列がパターンに一致するか調べるには?[C#/VB]」をご覧いただきたい。
using System.Text.RegularExpressions;
……省略……
string[] result
= Regex.Split("{分割対象文字列}", "{正規表現パターン}");
Imports System.Text.RegularExpressions
……省略……
Dim result As String() _
= Regex.Split("{分割対象文字列}", "{正規表現パターン}")
第1引数に分割対象の文字列を与え、第2引数にはセパレーターとなる正規表現パターンを与える。分割した結果は、文字列の配列として返される。
上に示した書式だけを見ると、Stringクラス(System名前空間)のSplitメソッドと同じように思える。だが、セパレーターの指定に正規表現パターンが使えることで、柔軟で多彩な分割処理が可能になっているのだ。後述するように、CSVデータを項目に分解する処理なども行える。
実際の例
まずは、正規表現パターンを使わないコンソールアプリの例を示そう(次のコード)。この場合は、StringクラスのSplitメソッドと同様の結果が得られる。コンソールに出力するための「DisplayAll」拡張メソッドをわざわざ作っているのは、以降のサンプルコードでも利用するためである。
using System.Text.RegularExpressions;
using static System.Console;
public static class StringArrayExtension
{
// 配列中の文字列を「'」で囲み、先頭に番号を付けて表示する
public static void DisplayAll(
this System.Collections.Generic.IEnumerable<string> words)
{
int count = 0;
foreach (var s in words)
WriteLine($"{++count}:'{s}'");
}
}
class Program
{
static void Main(string[] args)
{
// StringクラスのSplitメソッド
"吾輩は猫である。吾輩は子犬である。".Split('。')
.DisplayAll();
// 出力
// 1:'吾輩は猫である'
// 2:'吾輩は子犬である'
// 3:''
// RegexクラスのSplit静的メソッド
Regex.Split("吾輩は猫である。吾輩は子犬である。", "。")
.DisplayAll();
// 出力
// 1:'吾輩は猫である'
// 2:'吾輩は子犬である'
// 3:''
#if DEBUG
ReadKey();
#endif
}
}
Imports System.Text.RegularExpressions
Imports System.Console
Module StringArrayExtension
' 配列中の文字列を「'」で囲み、先頭に番号を付けて表示する
<System.Runtime.CompilerServices.Extension()>
Public Sub DisplayAll(words As IEnumerable(Of String))
Dim count As Integer = 0
For Each s In words
count += 1
WriteLine($"{count}:'{s}'")
Next
End Sub
End Module
Module Module1
Sub Main()
' StringクラスのSplitメソッド
Dim s = "吾輩は猫である。吾輩は子犬である。"
s.Split("。"c).DisplayAll()
' 1:'吾輩は猫である'
' 2:'吾輩は子犬である'
' 3:''
' RegexクラスのSplit静的メソッド
Regex.Split("吾輩は猫である。吾輩は子犬である。", "。") _
.DisplayAll()
' 1:'吾輩は猫である'
' 2:'吾輩は子犬である'
' 3:''
#If DEBUG Then
ReadKey()
#End If
End Sub
End Module
「吾輩は猫である。吾輩は子犬である。」という文字列を、「。」をセパレーターとして分割している。文字列末尾の「。」もセパレーターであるから、その後ろにも空の項目があると判断される(出力の3番目)。
なお、C#コードの冒頭にある「using static System.Console;」という書き方は、Visual Studio 2015からのものだ。詳しくは、「.NET TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]」をご覧いただきたい。同様な機能がVBには以前から備わっており、「.NET TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?」で解説している。
また、「Main」メソッド末尾にReadKeyメソッドを置く意味は、「.NET TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?」をご覧いただきたい。
拡張メソッドについては、MSDNの「拡張メソッド (C# プログラミング ガイド)」/「拡張メソッド (Visual Basic)」を参照してほしい。
上のコードでは冒頭で2つの名前空間をインポートしている。同じインポートが以降のサンプルコードでも必要であるが、以降では省略させていただく。
セパレーターとして2文字以上を使うには?
RegexクラスのSplitメソッドでは、正規表現にマッチする文字列がセパレーターになるので、連続した2文字以上の文字列をセパレーターとして使うこともできる。
例えば、連続して2文字以上続く英数字をセパレーターとして使うには次のコードのようにする(1文字の英数字はセパレーターにしない)。
Regex.Split("吾輩は1匹の猫であるAbc吾輩は1匹の子犬である987",
"[A-Z0-9][A-Z0-9]+", RegexOptions.IgnoreCase)
.DisplayAll();
// 出力
// 1:'吾輩は1匹の猫である'
// 2:'吾輩は1匹の子犬である'
// 3:''
Regex.Split("吾輩は1匹の猫であるAbc吾輩は1匹の子犬である987",
"[A-Z0-9][A-Z0-9]+", RegexOptions.IgnoreCase) _
.DisplayAll()
' 出力
' 1:'吾輩は1匹の猫である'
' 2:'吾輩は1匹の子犬である'
' 3:''
正規表現「[A-Z0-9][A-Z0-9]+」は、アルファベットの大文字か数字が2文字以上続くという意味だ(「[A-Z0-9]{2,}」でもよい)。さらにオプションとしてRegexOptions.IgnoreCaseを与えているので、アルファベットの小文字も含まれる。
英数字2文字以上の部分がセパレーターとなり、1文字の部分(この例では「1匹」の「1」)では分割されない。
セパレーターも結果に含めるには?
セパレーターの一部または全部を結果に含めることもできる。それには、正規表現の中で、結果として取り出したい部分をかっこで囲ってグループにする。
例えば、上の例で「Abc」と「987」がセパレーターになった。その先頭の文字「A」と「9」も分割結果に含めるには、次のコードのようにする。
Regex.Split("吾輩は1匹の猫であるAbc吾輩は1匹の子犬である987",
"([A-Z0-9])[A-Z0-9]+", RegexOptions.IgnoreCase)
.DisplayAll();
// 出力
// 1:'吾輩は1匹の猫である'
// 2:'A'
// 3:'吾輩は1匹の子犬である'
// 4:'9'
// 5:''
Regex.Split("吾輩は1匹の猫であるAbc吾輩は1匹の子犬である987",
"([A-Z0-9])[A-Z0-9]+", RegexOptions.IgnoreCase) _
.DisplayAll()
' 出力
' 1:'吾輩は1匹の猫である'
' 2:'A'
' 3:'吾輩は1匹の子犬である'
' 4:'9'
' 5:''
正規表現「([A-Z0-9])[A-Z0-9]+」の意味は先の例とほぼ同じだが、1文字目をグループに指定している。
グループとしてキャプチャーされたセパレーターの一部が、独立した項目として分割結果に含まれる(複数のグループを作れば、それぞれが別の項目として分割される)。
正規表現パターン:キャプチャーしないグループ化構成体「(?:)」
キャプチャーされたグループが結果に含まれるのは、意図と異なることもある。例えば、セパレーターとして「である。」と「であるが、」の2つを指定しようとして次のようなコードを書くと、OR条件の範囲を限定するために付けたかっこもグループとしてキャプチャーされてしまう。
Regex.Split("吾輩は猫である。吾輩は子犬であるが、猫ではない。",
"である(。|が、)")
.DisplayAll();
// 出力
// 1:'吾輩は猫'
// 2:'。'
// 3:'吾輩は子犬'
// 4:'が、'
// 5:'猫ではない。'
Regex.Split("吾輩は猫である。吾輩は子犬であるが、猫ではない。",
"である(。|が、)") _
.DisplayAll()
' 出力
' 1:'吾輩は猫'
' 2:'。'
' 3:'吾輩は子犬'
' 4:'が、'
' 5:'猫ではない。'
セパレーターの正規表現「である(。|が、)」は、「である。」または「であるが、」という意味だ。分割した結果としては、出力例の1番目/3番目/5番目の3つだけがほしいのである。
ところが、OR条件の範囲を限定するためのかっこがグループとしてキャプチャーされてしまい、「。」(2番目)と「が、」(4番目)も出力されてしまった。
非キャプチャーグループ化構成体「?:」は、かっこの中をキャプチャーさせないようにする(次のコード)。
Regex.Split("吾輩は猫である。吾輩は子犬であるが、猫ではない。",
"である(?:。|が、)")
.DisplayAll();
// 出力
// 1:'吾輩は猫'
// 2:'吾輩は子犬'
// 3:'猫ではない。'
Regex.Split("吾輩は猫である。吾輩は子犬であるが、猫ではない。",
"である(?:。|が、)") _
.DisplayAll()
' 出力
' 1:'吾輩は猫'
' 2:'吾輩は子犬'
' 3:'猫ではない。'
セパレーターの正規表現「である(?:。|が、)」の意味は先ほどとほぼ同じだが、非キャプチャーグループ指定「?:」により、かっこの中がキャプチャーされない。
CSVデータを分割する例
最後に、CSVのデータを分割する例を紹介しよう(次のコード)。
この正規表現の解読は、読者の皆さんへの宿題としたい。なお、これだけでは完璧とはいえないので注意してほしい。例えば、項目を囲む引用符「"」と項目中のエスケープされた引用符「""」はそのまま出力される(後処理として、項目の先頭と末尾から「"」を削る処理と、項目中の「""」を「"」に置換する処理が必要である)。
var csv = "猫, \"子犬\", , \"123,000\", 途中に\"\"引用符 , "
+ "途中に\r\n改行";
Regex.Split(csv, "\\s*,\\s*(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)").DisplayAll();
// 出力
// 1:'猫'
// 2:'"子犬"'
// 3:''
// 4:'"123,000"'
// 5:'途中に""引用符'
// 6:'途中に
// 改行'
Dim csv = "猫, ""子犬"", , ""123,000"", 途中に""""引用符 , " _
+ "途中に" + vbCrLf + "改行"
Regex.Split(csv, "\s*,\s*(?=(?:[^""]*""[^""]*"")*[^""]*$)").DisplayAll()
' 出力
' 1:'猫'
' 2:'"子犬"'
' 3:''
' 4:'"123,000"'
' 5:'途中に""引用符'
' 6:'途中に
' 改行'
CSVのデータは、項目ごとに「,」で区切られている。ただし、「"」で囲った中にある「,」では区切らない。そのルールで分割するロジックを書くと複雑なコードになってしまうが、正規表現を使えば1行で分割できるのだ。
なお、CSVの規格「RFC 4180」では項目の前後の空白も項目に含めるべきとなっているが、ここではあえて削るようにしている。
また、CSVデータの分割には、Visual BasicのTextFieldParserクラス(Microsoft.VisualBasic.FileIO名前空間)を利用する方法もある(項目途中の改行には対応していない)。その方法は「.NET TIPS:CSVファイルを読み込むには?[2.0のみ、C#、VB]」をご覧いただきたい。その際、StringオブジェクトをTextFieldParserクラスに処理させるには、次のようにしてMemoryStreamオブジェクト(System.IO名前空間)に変換しておく。
Dim stream = New MemoryStream(System.Text.Encoding.UTF8.GetBytes(csv))
また、C#からVisual Basicの機能を使う方法は、「.NET TIPS:VB.NET固有の関数をC#で使用するには?」をご覧いただきたい。
まとめ
正規表現を使って文字列を分割するには、RegexクラスのSplit静的メソッドを使えばよい(同じ処理を繰り返し実行すると分かっているなら、IsMatchメソッドの場合と同様にコンパイルオプション付きでRegexクラスのインスタンスを作り、Splitインスタンスメソッドを使う)。
また、本稿では正規表現を使ってCSVデータを分割する例も紹介した。正規表現について詳しくはMSDNの「.NET Framework の正規表現」をご覧いただきたい。
利用可能バージョン:.NET Framework 1.1以降(サンプルコードにはそれ以降の構文も含む)
カテゴリ:クラス・ライブラリ 処理対象:文字列
使用ライブラリ:Regexクラス(System.Text.RegularExpressions名前空間)
関連TIPS:正規表現を使ってパターンに一致する全ての文字列を抽出するには?[C#/VB]
関連TIPS:正規表現を使って文字列がパターンに一致するか調べるには?[C#/VB]
関連TIPS:正規表現を使って文字列を置換するには?[C#/VB]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
関連TIPS:正規表現を使って部分文字列を取得するには?[C#、VB]
関連TIPS:正規表現のパターン内にコメント文を記述するには?[C#、VB]
関連TIPS:CSVファイルを読み込むには?[2.0のみ、C#、VB]
関連TIPS:VB.NET固有の関数をC#で使用するには?
関連TIPS:文字列を文字列により分割するには?(VB.NET関数活用)
関連TIPS:Stringクラスにより文字列を文字列で分割するには?[2.0のみ、C#、VB]
Copyright© Digital Advantage Corp. All Rights Reserved.