2013年10月21日月曜日

grep -Pで日本語のUnicodeプロパティが使えない件について

Mountain Lionのgrepは-Pオプション(Perl互換の正規表現)をサポートしなくなった。巷ではThe Silver Searcherが流行っているみたいだけど、かたくなにgrep -Pを使い続けるならGNU grepをインストールすればいい。

$ brew tap homebrew/dupes
$ brew install grep

これで-Pオプションは使えるようになったけど、日本語のUnicodeプロパティはマッチしない(-vはパターンの反転なのでマッチしない行が表示される)。

(追記)バージョン2.15でマッチするように修正された。

$ echo "あ" | ggrep -v -P "\p{Hiragana}"
あ

ggrepになっているのはtypoではなくて、コマンド名が衝突しないように、Homebrewがプレフィックスを付けてインストールするため。

GNU grepはPerl互換の正規表現ライブラリであるPCRE(Perl Compatible Regular Expressions)に依存していて、Unicodeプロパティを使う場合はPCREのconfigureで有効にする必要がある。念のためpcretestコマンドで確認してみる。

$ pcretest -C
PCRE version 8.33 2013-05-28
Compiled with
  8-bit support
  UTF-8 support
  Unicode properties support
  Just-in-time compiler support: x86 64bit (little endian + unaligned)
  Newline sequence is LF
  \R matches all Unicode newlines
  Internal link size = 2
  POSIX malloc threshold = 10
  Default match limit = 10000000
  Default recursion depth limit = 10000000
  Match recursion uses stack

有効になっていた。

ここで、pcregrepというコマンドがインストールされていることに気付いたので、試してみたけどやっぱりマッチしない。

$ echo "あ" | pcregrep -v "\p{Hiragana}"
あ

ヘルプを見ると、UTF-8モードを有効にする-uオプションというのがあったので、これを指定してみたらマッチした。

$ echo "あ" | pcregrep -u "\p{Hiragana}"
あ

UTF-8モードについて調べるためman pcreしてみると、non-UTF-8 modeではUnicodeプロパティのテストは255以下のコードポイントに制限されると書いてあったので、GNU grepでもなんとかしてUTF-8モードにする必要がある。さらにドキュメントを読むと、パターンの先頭に(*UTF8)があればUTF-8モードになるとのことなので試してみる。

$ echo "あ" | ggrep -P "(*UTF8)\p{Hiragana}"
あ

できた。だけど英数記号混じりで毎回7文字もタイプするのはだるい。

ということで、grepのソースを編集してしまうことにした。UTF-8モードにするためには、pcre_compile()PCRE_UTF8を渡せばいいようなので、やってみたら期待通りに動くようになった。

diff --git a/src/pcresearch.c b/src/pcresearch.c
index 2994e65..4a62894 100644
--- a/src/pcresearch.c
+++ b/src/pcresearch.c
@@ -45,7 +45,7 @@ Pcompile (char const *pattern, size_t size)
   int e;
   char const *ep;
   char *re = xnmalloc (4, size + 7);
-  int flags = PCRE_MULTILINE | (match_icase ? PCRE_CASELESS : 0);
+  int flags = PCRE_UTF8 | PCRE_MULTILINE | (match_icase ? PCRE_CASELESS : 0);
   char const *patlim = pattern + size;
   char *n = re;
   char const *p;
$ echo "あ" | ggrep -P "\p{Hiragana}"
あ

上記のパッチを適用するようにHomebrewのFormulaを変更するパッチ。

diff --git a/grep.rb b/grep.rb
index 8fe2284..5ec52d7 100644
--- a/grep.rb
+++ b/grep.rb
@@ -36,4 +36,22 @@ class Grep < Formula
     If you do not want the prefix, install using the 'default-names' option.
     EOS
   end
+
+  def patches
+    DATA
+  end
 end
+__END__
+diff --git a/src/pcresearch.c b/src/pcresearch.c
+index 2994e65..4a62894 100644
+--- a/src/pcresearch.c
++++ b/src/pcresearch.c
+@@ -45,7 +45,7 @@ Pcompile (char const *pattern, size_t size)
+   int e;
+   char const *ep;
+   char *re = xnmalloc (4, size + 7);
+-  int flags = PCRE_MULTILINE | (match_icase ? PCRE_CASELESS : 0);
++  int flags = PCRE_UTF8 | PCRE_MULTILINE | (match_icase ? PCRE_CASELESS : 0);
+   char const *patlim = pattern + size;
+   char *n = re;
+   char const *p;

だけど、pcregrepはGNU grepとの互換性が高いようなので、これをそのまま使うのもありな気がする。大きな違いは、BSD grepと同じくパスをオプションの後に置くことと、--include,--excludeを正規表現で指定すること。pcregrep特有のオプションもいくつかある。