可変長の否定戻り読み
「foo でないものの後に続いている bar」にマッチさせたいという場合には否定戻り読み (negative lookbehind) が使える.
上の例を表す正規表現は
(?<!foo)bar
となる.
「foo または hoge でないものの後に続いている bar」も同様に
(?<!foo|hoge)bar
と書けたら嬉しいのだが,可変長の戻り読みを許しているエンジンは少ないらしい.
lazyeagle: Variable length lookbehind not implemented in regex だと… http://twitter.com/lazyeagle/status/3841821998 finalfusion: @lazyeagle 戻り読みで可変長許しているのは少数派ですよ。 http://twitter.com/finalfusion/status/3841869401 lazyeagle: @finalfusion そうだったんですか… 少数派ということは,許している正規表現エンジンもあるんですか? http://twitter.com/lazyeagle/status/3841918295 finalfusion: @lazyeagle 確かJavaと、.NETのが使えたような http://twitter.com/finalfusion/status/3842015369 finalfusion: Perlの場合、\Kを使うと(5.10から追加)戻り読みなしでも同じようなマッチをするぱたーんをかけなくもないです http://twitter.com/finalfusion/status/3842033645
鬼車 (Ruby 1.9)
鬼車では | の場合のみ許されているようだ.
つまり,上記のような例なら可能だが,例えば
(?<!o+)bar
のようなものはダメらしい.
実際に Version 5.9.1 で試してみたら
(?<!foo|hoge)bar
は期待通りに foobar, hogebar にはマッチせず fugabar にはマッチしたが,
(?<!o+)bar
は onig_new のときに invalid pattern in look-behind というエラーが返った.
鬼車を採用している Ruby 1.9 でも同様の結果となる.
irb(main):001:0> /(?<!foo|hoge)bar/.match 'foobar' => nil irb(main):002:0> /(?<!foo|hoge)bar/.match 'hogebar' => nil irb(main):003:0> /(?<!foo|hoge)bar/.match 'fugabar' => #<MatchData "bar"> irb(main):004:0> /(?<!o+)bar/.match 'foobar' SyntaxError: (irb):4: invalid pattern in look-behind: /(?<!o+)bar/ from /opt/local/bin/irb1.9:12:in `<main>'
Java
教えていただいた Java を試してみた.
import java.util.regex.*; public class RegexTest { public static void main(String[] args) { Pattern p = Pattern.compile(".*(?<!foo|hoge)bar"); System.out.println(p.matcher("foobar").matches()); // => false System.out.println(p.matcher("hogebar").matches()); // => false System.out.println(p.matcher("fugabar").matches()); // => true Pattern q = Pattern.compile(".*(?<!o+)bar"); System.out.println(q.matcher("foobar").matches()); // => false System.out.println(q.matcher("fugabar").matches()); // => true } }
素晴しい!
Perl (PCRE)
Perl 5.10 から導入された \K を使うことで可変長の肯定戻り読みは可能になることがわかったが,否定戻り読みはどうすればいいのかわからなかった…
use strict; use warnings; sub test1 { my $str = shift; if ($str =~ /(foo|hoge)\Kbar/) { print "true\n"; } else { print "false\n"; } } test1('foobar'); # => true test1('hogebar'); # => true test1('fugabar'); # => false sub test2 { my $str = shift; if ($str =~ /(o+)\Kbar/) { print "true\n"; } else { print "false\n"; } } test2('foobar'); # => true test2('hogebar'); # => false
一応こうすることで,| のみなら可能になる.
use strict; use warnings; sub test { my $str = shift; if ($str =~ /(?<!foo|(?<=h)oge)bar/) { print "true\n"; } else { print "false\n"; } } test('foobar'); # => false test('hogebar'); # => false test('fugabar'); # => true
参照
http://www.geocities.jp/kosako3/oniguruma/doc/RE.ja.txt の「否定戻り読み」のところ
http://java.sun.com/javase/ja/6/docs/ja/api/java/util/regex/Pattern.html
http://www.pcre.org/pcre.txt の「Resetting the match start」のところと「Lookbehind assertions」のところ