|
|
正規表現の基本 |
H.Kamifuji . |
正規表現を使ってどのようなことができるのか、そしてその基本的な手順について確認していきます。 当ページでは、Linux CentOS7 の Gnome で動作テストしています。 現在(2021/08)では、JDK-16.0.2 にアップされています。一部、上位互換について、見直しを行っていきます。 現在(2021/11)では、JDK-17.0.1 にアップされています。一部、上位互換について、見直しを行っていきます。 現在(2023/04)では、JDK-20.0.1 にアップされています。一部、上位互換について、見直しを行っていきます。 現在(2024/10)では、JDK-23 にアップされています。一部、上位互換について、見直しを行っていきます。 |
|
まず正規表現をどのような時に使用するのかを簡単に確認しておきます。 ある文字列が他の文字列と一致するかどうか調べるには、Stringクラスで定義されているequalsメソッドを使います。 equals public boolean equals(Object anObject)この文字列と指定されたオブジェクトを比較します。引数が null でなく、このオブジェクトと同じ文字シーケンスを表す String オブジェクトである場合にだけ、結果は true になります。 パラメータ: anObject - この String と比較されるオブジェクト 戻り値: 指定されたオブジェクトがこの文字列に等しい String を表す場合は true、そうでない場合は false 例えば次のように使います。 String str = "Hello"; if (str.equals("Hello")){ System.out.println("文字列はHelloです"); }else{ System.out.println("文字列はHelloとは違います"); }このサンプルでは変数に格納された文字列と"Hello"を比較し、完全に一致するかどうかを調べます。今回の場合は一致しますので「文字列はHelloです」と出力されることになります。 このように完全に一致しているかどうかを調べるだけならばequalsメソッドを使うことで判定が出来ます。 では次に対象の文字列に"e"で始まり"o"で終わる文字列が含まれているかどうかを調べるにはどうすればいいでしょうか。このような条件を満たす文字列は数限りなく存在します。例えば次のような文字列です。 eo eao poebon eabogon abbdddeeebo Hello強引に調べるのであれば文字列の中に"e"と"o"が含まれているのを確認し"e"の方が先に記述されているかどうかを調べるといった方法でも可能です。ただもっと複雑な条件になった場合には大変ですし、条件毎に調べるアルゴリズムを考え、検証しなければなりません。 このような時、正規表現を使用することで簡潔に調べることができます。正規表現では調べたい条件をパターンと呼びますが、先ほどの"e"で始まり"o"で終わるパターンは次のように表すことが出来ます。 "e.*o"このパターンは「"e"で始まり任意の文字が0回以上繰り返された後で"o"が出現する」というパターンとなります。パターンを定義したら今度は対象の文字列にパターンに一致する文字列があるかどうかを調べます。正規表現では対象の文字列がパターンに一致した文字列を含んでいた場合、「パターンにマッチした」と言います。 詳しい記述方法は次のページ以降で確認していきますが、次のように記述します。 String str = "poebon"; String regex = "e.*o"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()){ System.out.println("マッチしました"); }else{ System.out.println("マッチしません"); }正規表現を使えば条件式をパターンとして表しさえすれば後は同じ手順でマッチするかどうか確認できます。またパターンを簡単に作成できるように便利な構文が多く用意されています。 では次のページ以降で正規表現の実際の使い方を確認していきます。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample1_1{ public static void main(String args[]){ String str1 = "Hello"; String str2 = "Hey"; String regex = "e.*o"; Pattern p = Pattern.compile(regex); Matcher m1 = p.matcher(str1); System.out.print(str1 + " は"); if (m1.find()){ System.out.println("マッチします"); }else{ System.out.println("マッチしません"); } Matcher m2 = p.matcher(str2); System.out.print(str2 + " は"); if (m2.find()){ System.out.println("マッチします"); }else{ System.out.println("マッチしません"); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ini]$ javac JSample1_1.java [xxxxxxxx@dddddddddd Ini]$ java JSample1_1 Hello はマッチします Hey はマッチしません [xxxxxxxx@dddddddddd Ini]$なお今後作成するサンプルは、全て文字コードをUTF-8で保存されています。 |
正規表現を利用する上でまず行うことはパターンを作成することです。パターンとは何にマッチさせるのかを定義したものと考えて下さい。 例えば"2017"という4文字が含まれているかどうか調べたい場合もありますし、"color"という5文字が先頭から現れるかどうか調べたい場合もあります。この調べたい条件を文字列として表します。 String regex = "abc";上記の場合は"abc"という3つの文字が連続して現れるかどうかを調べるパターンを表す文字列です。この場合は例えば次のようなターゲット文字列とマッチします。
abc またパターンでは特殊な意味を持つ文字と組み合わせることもできます。 String regex = "^abc";上記の場合は"abc"という3つの文字が連続して現れるかどうかを調べるパターンを表す文字列です。この場合は例えば次のようなターゲット文字列とマッチします。
abc java.util.regex.Patternクラスパターンを表す文字列を定義したら、次にパターンオブジェクトを作成します。パターンオブジェクトは文字列として記述されたパターンをJavaがプログラムの中で使用できる形式に変換したものと考えて下さい。パターンはjava.util.regex.Patternクラスで定義されています。このクラスはコンストラクタは用意されておらず、次のstaticメソッドを使って作成します。 compile public static Pattern compile(String regex)指定された正規表現をパターンにコンパイルします。 パラメータ: regex - コンパイルされる表現 例外: PatternSyntaxException - 表現の構文が無効である場合 引数にはパターンを表す文字列を指定して下さい。 例えば次のように記述します。 String regex = "abc"; Pattern p = Pattern.compile(regex);またパターンオブジェクトを作成する時にはオプションを指定することができます。 compile public static Pattern compile(String regex, int flags)指定されたフラグを使用して、指定された正規表現をパターンにコンパイルします。 パラメータ: regex - コンパイルされる表現 flags - マッチフラグ。CASE_INSENSITIVE、MULTILINE、DOTALL、UNICODE_CASE、CANON_EQ、 UNIX_LINES、LITERAL、および COMMENTS を含めることのできるビットマスク 例外: IllegalArgumentException - 定義済みマッチフラグに対応するビット値以外の値が flags に設 定されている場合 PatternSyntaxException - 表現の構文が無効である場合 1番目の引数には条件を記述した文字列を指定して下さい。そして2番目の引数にオプションのフラグを設定します。例えば「CASE_INSENSITIVE」を指定すると大文字と小文字を区別しなくなります。このオプションの使い方は別のページで詳しく確認します。 パターンを作成したら、次の手順はパターンとターゲット文字列を指定してマッチャを作成します。これは次のページで解説します。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample2_1{ public static void main(String args[]){ String str1 = "abcdef"; String str2 = "defabc"; String regex1 = "abc"; Pattern p1 = Pattern.compile(regex1); String regex2 = "^abc"; Pattern p2 = Pattern.compile(regex2); check(p1, str1); check(p1, str2); check(p2, str1); check(p2, str2); } private static void check(Pattern p, String target){ Matcher m = p.matcher(target); System.out.print(target + " は " + p.pattern() + " に"); if (m.find()){ System.out.println("マッチします"); }else{ System.out.println("マッチしません"); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ini]$ javac JSample2_1.java [xxxxxxxx@dddddddddd Ini]$ java JSample2_1 abcdef は abc にマッチします defabc は abc にマッチします abcdef は ^abc にマッチします defabc は ^abc にマッチしません [xxxxxxxx@dddddddddd Ini]$今回は2つのパターンを作成し、それぞれ2つのターゲット文字列に対してマッチするかどうかを試しました。 なお上記のサンプルではパターンオブジェクトから元になっている条件式を表す文字列をPatternクラスで定義されているpatternメソッドを使って取得しています。 pattern public String pattern()このパターンのコンパイル元の正規表現を返します。 戻り値: このパターンのコンパイル元 メソッドを実行するとcompileメソッドの引数に指定した文字列を取得できます。 |
パターンオブジェクトを作成したら、次はターゲットとなる文字列を対象にしてマッチするかどうかを調べます。この時に使用するのがマッチャです。マッチャはパターンを使ってターゲットの文字列に対して様々な操作を行うオブジェクトです。 マッチャはjava.util.regex.Matcherクラスで定義されています。このクラスはコンストラクタは用意されておらず、Patternクラスで定義されているmatcherを使って作成します。 matcher public Matcher matcher(CharSequence input)指定された入力とこのパターンをマッチする正規表現エンジンを作成します。 パラメータ: input - マッチされる文字シーケンス 戻り値: このパターンの新しい正規表現エンジン 引数にはターゲットとなる文字列を、CharSequenceインターフェースを実装したクラスのオブジェクトを指定して下さい。Stringクラスなどが該当します。 例えば次のように記述します。 String str = "abcdef"; String regex = "abc"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str);この場合、"abc"で表されるパターンとターゲットの文字列である"abcdef"を対象としたマッチャが作成されます。 java.util.regex.Matcherクラスマッチャが作成されたら、マッチャに対して様々な操作を指示できます。例えば、ターゲットの文字列にパターンを適用しマッチしているかどうか調べるにはMatcherクラスで定義されているfindメソッドを使います。 find public boolean find()入力シーケンスからこのパターンとマッチする次の部分シーケンスを検索します。 このメソッドは、正規検索エンジンの領域の先頭から開始されます。 ただし、前回の呼び出しが正常に終了してから正規表現エンジンがリセットされていない場合は、前回のマッチで一致しなかった最初の文字から開始されます。 マッチが成功した場合は、start、end、および group メソッドを使用して詳細情報を取得できます。 戻り値: 入力シーケンスの部分シーケンスがこの正規表現エンジンのパターンとマッチした場合にのみ true 作成したマッチャオブジェクトに対してこのメソッドを実行すると、ターゲット文字列がパターンにマッチしているかどうか調べ結果をboolean型の値で返します。 例えば次のように記述します。 String str = "abcdef"; String regex = "abc"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()){ System.out.println("マッチしました"); }他にも数多くのメソッドが用意されており、ターゲット文字列のどの位置からマッチしたかどうか、実際にマッチした文字列はどの部分なのかなどをマッチャを通して取得することができます。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample3_1{ public static void main(String args[]){ String str1 = "stat[01]end"; String str2 = "array[int]"; String regex = "\\[.*\\]"; Pattern p = Pattern.compile(regex); check(p, str1); check(p, str2); } private static void check(Pattern p, String target){ Matcher m = p.matcher(target); if (m.find()){ System.out.println("マッチします"); System.out.println("マッチした部分は " + m.group() + " です"); }else{ System.out.println("マッチしません"); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ini]$ javac JSample3_1.java [xxxxxxxx@dddddddddd Ini]$ java JSample3_1 マッチします マッチした部分は [01] です マッチします マッチした部分は [int] です [xxxxxxxx@dddddddddd Ini]$今回は"["で始まり"]"で終わる文字列があるかどうかのパターンを作成しマッチするかどうか調べています。またマッチした場合は、ターゲット文字列の中で実際にマッチした部分を表示しています。 |
前のページで確認したとおり、ターゲットの文字列にパターンを適用しマッチしているかどうか調べるにはMatcherクラスで定義されているfindメソッドを使います。 例えば次のように記述します。 String str = "Tomato is 100yen, Lemon is 80yen."; String regex = "\\d.+?yen"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()){ System.out.println("マッチしました"); }findメソッドは最初に呼び出された時にターゲット文字列の最初からマッチするかどうかを見ていきます。よって今回の場合はまず"100yen"にマッチします。 Tomato is 100yen, Lemon is 80yen. ここでもう一度findメソッドを呼び出してみます。findメソッドは前回の呼び出しが正常に終了した場合、前回のマッチで一致しなかった最初の文字からマッチするかどうかを確認します。つまり"100yen"の次の","の位置です。 Tomato is 100yen, Lemon is 80yen. ^この位置","の位置からパターンにマッチするのは"80yen"です。 Tomato is 100yen, Lemon is 80yen. ここでもう一度findメソッドを呼び出してみます。今度の場合も同じく前回のマッチで一致しなかった最初の文字からマッチするかどうかを確認します。つまり"80yen"の次の"."の位置です。 Tomato is 100yen, Lemon is 80yen. ^この位置"."の位置からパターンにマッチするのは存在しないため、findメソッドは失敗します。 このようにfindメソッドを使うことでパターンがターゲット文字列にマッチするかどうか確認することができ、さらにfindに成功した場合には続けて他にマッチするものがないか調べることができます。 findメソッドは成功すると戻り値としてtrueを返しますので、1つのターゲット文字列にマッチする文字列を順に取り出すには次のように記述することができます。 String str = "Tomato is 100yen, Lemon is 80yen."; String regex = "\\d.+?yen"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); while(m.find()){ System.out.println(m.group()); }※ groupメソッドはマッチした文字列を取得するメソッドです。別のページで解説します。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample6_1{ public static void main(String args[]){ String str = "Tomato is 100yen, Lemon is 80yen, Banana is 140yen."; String regex = "\\d.+?yen"; Pattern p = Pattern.compile(regex); System.out.println("Target:" + str); System.out.println("Pattern:" + regex + "\n"); check(p, str); } private static void check(Pattern p, String target){ Matcher m = p.matcher(target); while(m.find()){ System.out.println("match:" + m.group()); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ini]$ javac JSample6_1.java [xxxxxxxx@dddddddddd Ini]$ java JSample6_1 Target:Tomato is 100yen, Lemon is 80yen, Banana is 140yen. Pattern:\d.+?yen match:100yen match:80yen match:140yen [xxxxxxxx@dddddddddd Ini]$ |
パターンを表す文字列の中には任意の文字を記述することが出来ますが、特殊な用途で使用される文字にはエスケープ処理が必要となります。 \ * + . ? { } ( ) [ ] ^ $ - |これらの文字はパターンの中で使用されると特別な意味を持ちます。そこで単なる文字として扱いたい場合には「\」記号を使ってエスケープ処理を行います。例えば次のように記述します。 \\ \* \|"\*"と書いた場合は特殊な意味を持たない"*"という1つの文字として扱われます。 また「-」については[]の中で使用される場合にだけ特別な意味を持つため、[]の中で使用される時にエスケープ処理が必要となります。 エスケープシーケンスパターンを表す文字列の中には次のエスケープシーケンスも記述することができます。\0n 8 進値 0n を持つ文字 (0 <= n <= 7) \0nn 8 進値 0nn を持つ文字 (0 <= n <= 7) \0mnn 8 進値 0mnn を持つ文字 (0 <= m <= 3、0 <= n <= 7) \xhh 16 進値 0xhh を持つ文字 \uhhhh 16 進値 0xhhhh を持つ文字 \t タブ文字 ('\u0009') \n 改行文字 ('\u000A') \r キャリッジリターン文字 ('\u000D') \f 用紙送り文字 ('\u000C') \a 警告 (ベル) 文字 ('\u0007') \e エスケープ文字 ('\u001B') \cx x に対応する制御文字他にも特別な意味を持つエスケープシーケンスが用意されていますが、必要になった時に随時解説していきます。 文字列の中に記述する場合の注意点例えば"^"という文字に一致するパターンを作成する場合を考えてみます。"^"はパターンの中では特別な意味を持つ文字のため単なる文字として扱いたい場合は\記号によるエスケープ処理が必要です。実際には次のように記述します。\^そこで次のように記述してしまうと間違いとなります。 String regex = "\^"; Pattern p = Pattern.compile(regex);文字列の中で\記号は特別な意味を持ちます。その為、今度は"\"に対して\記号によるエスケープ処理が必要となります。実際には次のように記述します。 String regex = "\\^"; Pattern p = Pattern.compile(regex);最初のエスケープはパターンを表す文字列の記述ルールに従って行ったもので、次のエスケープはJava言語の文字列の中で\記号を記述する場合のルールに従ったものです。"\\"と続けて記述されていてもそれぞれ意味が違う場合がありますので注意して下さい。 では実際に試してみます。 サンプルプログラム下記のサンプルを実行してみよう。import java.util.regex.Pattern; import java.util.regex.Matcher; class JSample4_1{ public static void main(String args[]){ String str1 = "a"; String str2 = "."; String regex1 = "."; Pattern p1 = Pattern.compile(regex1); String regex2 = "\\."; Pattern p2 = Pattern.compile(regex2); check(p1, str1); check(p1, str2); check(p2, str1); check(p2, str2); } private static void check(Pattern p, String target){ Matcher m = p.matcher(target); System.out.print(target + " は " + p.pattern() + " に"); if (m.find()){ System.out.println("マッチします"); }else{ System.out.println("マッチしません"); } } }上記をコンパイルした後で実行すると次のように表示されます。 [xxxxxxxx@dddddddddd Ini]$ javac JSample4_1.java [xxxxxxxx@dddddddddd Ini]$ java JSample4_1 a は . にマッチします . は . にマッチします a は \. にマッチしません . は \. にマッチします [xxxxxxxx@dddddddddd Ini]$今回はまず"."で表されるパターンにマッチするかどうか調べています。パターンでは"."は任意の一文字を表す特殊な文字のため、"a"でも"."でもマッチします。それに対して"\."は"."という単なる文字を表すパターンの為、"a"にはマッチしませんが"."にはマッチします。 |
ターゲット文字列は1行だけではなく複数の行から構成される場合があります。正規表現を利用する場合、ターゲット文字列の中に次の文字または文字列が現れた時に行末記号として扱われます。 改行文字 ('\n') 直後に改行文字が付いたキャリッジリターン文字 ("\r\n") 単独のキャリッジリターン文字 ('\r') 次行文字 ('\u0085') 行区切り文字 ('\u2028') 段落区切り文字 ('\u2029)これらの文字または文字列が現れると次の行として扱われます。例えば次のように記述します。 String str = "abc\ndef";この場合、ターゲット文字列は2つの行から構成されていると識別されることになります。行を識別するかどうかは、パターンを行頭や行末にマッチさせる場合に重要です。 なおUNIX_LINESモードが有効な場合は、改行文字 ('\n')だけが行末記号として認識されます。 String regex = "abc"; Pattern p = Pattern.compile(regex, Pattern.UNIX_LINES);具体的な利用方法などは必要に応じて今後解説していきます。 |
|
|