実際にやってて非常に使うがいつもぼんやり忘れてしまう正規表現の知識についてメモっとく

忘れることはメモっとくのが一番。

正規表現における()括弧について

  1. メタ文字「|」縦棒(つまりor)の範囲を限定する
  2. 量指定子の対象をグループ化する
  3. 後方参照(マッチ部分をキャプチャし、後から正規表現内、またはプログラムでキャプチャ部分を使用する \1/\2/\3や$1/$2/$3などで参照できる)

括弧についてのいくつかの例:perlにて

1.メタ文字「|」縦棒(つまりor)の範囲を限定する

$text = "I like cats. You like dogs.";
$text =~ s/(cat|dog)s/pigs/;
print "1.$text\n";

結果は

1.I like pigs. You like dogs.

dogsもpigsに変えたいので、/g修飾子で文字列全部を置換する

#/g修飾子の効果
$text = "I like cats. You like dogs.";
$text =~ s/(cat|dog)s/pigs/g;
print "2.$text\n";

結果は

2.I like pigs. You like pigs.

2.量指定子の対象をグループ化する

$text = "I like catcats. You like dogdogs.";
$text =~ s/(cat|dog)s/pigs/g;
print "3-1.$text\n";

結果は

3-1.I like catpigs. You like dogpigs.

一回以上の繰り返しを意味する量指定子「+」をつけてみる

$text = "I like catcats. You like dogdogs.";
$text =~ s/(cat|dog)+s/pigs/g;
print "3-2.$text\n";

結果は

3-2.I like pigs. You like pigs.

3.後方参照の使用

#後方参照
$text = "I like cat. You like dog.";
$text =~ s/(cat|dog)/\1s/g;
print "4.$text\n";

結果は

4.I like cats. You like dogs.
#全てのタグ内の罵り言葉を消したいです
$text = "<b>ふざけないでよ!</b><p>なめないでよ!</p><span>みないでよ!</span>";
$text =~ s/(<\w+>)(.+)(<\/\w+>)/\1\3/g;
print "5.$text\n";

結果は

5.<b></span>

量指定子は欲張りにマッチを試みるので、二つ目の括弧内「.+」で任意の文字を表す.(ドット)が 。</span>の前までマッチを試みてしまい思ったような結果にならなかった。



だからこうすれば想定の通りの変換を得られるよね

#全てのタグ内の罵り言葉を消したいです
$text = "<b>ふざけないでよ!</b><p>なめないでよ!</p><span>みないでよ!</span>";
$text =~ s/(<(\w+)>)(.+)(<\/\2+>)/\1\4/g;
print "6.$text\n";
6.<b></b><p></p><span></span>

または、欲張りなマッチを抑制するように量指定子に?をつけることでも対処可能

#タグ内の罵りを消したい。(欲張り抑制バージョン)
$text = "<b>ふざけないでよ!</b><p>なめないでよ!</p><span>みないでよ!</span>";
$text =~ s/(<\w+>)(.+?)(<\/\w+>)/\1\3/g;
print "7.$text\n";
7.<b></b><p></p><span></span>


「\1」の数値は「(」開き括弧の登場順番に対応している。ネストがあろうがなんであろうが左から登場した順番が1番目が「\1」つぎが「\2」として参照可能である。


ここでは後方参照の変数として「\1」を使用しているが、「$1」という変数で参照する場合もある。
何が違うのか?

1.「\1」「\2」「\3」の場合
同じ正規表現の中ですでにマッチした何らかのテキストを参照するため
2.「$1」「$2」「$3」の場合
マッチが成功して終わった後のコードから同じテキストを参照するため

明示的にキャプチャしない括弧

正規表現における括弧は、たとえキャプチャの必要がなくグループ化の用途としてのみ使いたい場合でも自動でキャプチャしてしまいます。

グループ化のみの括弧として括弧を使用する場合は、(?:〜)が使える。これは「(?:」が開きの括弧であり、「)」が閉じの括弧である。この2つの括弧に囲まれた範囲はグループ化は行われるがキャプチャされることはない。リソースの節約になるとかなんとかかんだとか。


「位置にマッチする」ことの理解の重要と「先読み後読み」について

先読みってのは「右に〜があったら」って考えてます。正規表現エンジンは基本どの言語の実装でも左から右へマッチを試みていくみたいなんで、先読みというのは右読みということ。
後読みは「左に〜があったら」って考えます。いろいろ難しいことを考えるともしかするとこの表現はよくないのかもしれないけれど、とりあえず。

こんだJavaで書きます

  // 先読み 右に〜があったら:(?=です)と書く 右に「です」が存在する位置にマッチする
	@Test
	public void right() throws Exception {
		String value = "わからないです。気持ちが悪いです。絶対にわからないです。";
		String reg = "(?=です)";
		Pattern pt = Pattern.compile(reg);
		Matcher m = pt.matcher(value);
		String all = m.replaceAll("そう");
		System.out.println(all);
	}

	//後読み 左に〜があったら:(?<=です)と書く 左に「です」が存在する位置にマッチする
	@Test
	public void left() throws Exception {
		String value = "わからないです。気持ちが悪いです。絶対にわからないです。";
		String reg = "(?<=です)";
		Pattern pt = Pattern.compile(reg);
		Matcher m = pt.matcher(value);
		String all = m.replaceAll("よ");
		System.out.println(all);
	}

否定は上記の否定になります。右に〜がなかったら。左に〜がなかったら。(?!です) or (?<:!です)

PerlでのiオプションやmオプションのJavaでの使い方

  //iオプションなしの場合(大文字小文字区別する)
	@Test
	public final void nooptionTest() {
		String text = "I am NEKO. You are neko. She is Nekozura.";
		Pattern pt = Pattern.compile("Neko\\.");
		Matcher m = pt.matcher(text);
		String replaced = m.replaceAll("Inu.");
		System.out.println(replaced + "\r\n");
	}
	
	//perlでの/iオプション(大文字小文字区別しない)をjavaで、その1
	@Test
	public final void ioptionTest() {
		String text = "I am NEKO. You are neko. She is Nekozura.";
		Pattern pt = Pattern.compile("Neko\\.", Pattern.CASE_INSENSITIVE);
		Matcher m = pt.matcher(text);
		String replaced = m.replaceAll("Inu.");
		System.out.println(replaced + "\r\n");
	}
	
	//perlでの/iオプション(大文字小文字区別しない)をjavaで、その2
	@Test
	public final void ioptionTest2() {
		String text = "I am NEKO. You are neko. She is Nekozura.";
		Pattern pt = Pattern.compile("(?i)Neko\\.");
		Matcher m = pt.matcher(text);
		String replaced = m.replaceAll("Inu.");
		System.out.println(replaced + "\r\n");
	}
	
	//perlでの/mオプション(拡張行アンカーマッチモード複数行モード)ではない
	@Test
	public final void noptionTest() {
		String text = "I am NEKO. \nYou are neko. \nShe has Nekozura.";
		Pattern pt = Pattern.compile("^");
		Matcher m = pt.matcher(text);
		String replaced = m.replaceAll("冒頭っす ");
		System.out.println(replaced + "\r\n");
	}
	
	//perlでの/mオプション(拡張行アンカーマッチモード複数行モード)をjavaで、その1
	@Test
	public final void moptionTest() {
		String text = "I am NEKO. \nYou are neko. \nShe has Nekozura.";
		Pattern pt = Pattern.compile("(?m)^");
		Matcher m = pt.matcher(text);
		String replaced = m.replaceAll("冒頭っす ");
		System.out.println(replaced + "\r\n");
	}
	
	//perlでの/mオプション(拡張行アンカーマッチモード複数行モード)をjavaで、その2
	@Test
	public final void moptionTest2() {
		String text = "I am NEKO. \nYou are neko. \nShe has Nekozura.";
		Pattern pt = Pattern.compile("^" , Pattern.MULTILINE);
		Matcher m = pt.matcher(text);
		String replaced = m.replaceAll("冒頭っす ");
		System.out.println(replaced + "\r\n");
	}