m2

後方参照の直後に他の数字が続くような置換パターン

2011/08/11 追記


このエントリで紹介した Opera11 の挙動は Opera12 の開発版で修正されたようです。

これも私が報告しました。発見したのは miya2000 さんだったと思います。

alert("123456789X".replace(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/, '$010'));

これで本来は10と出るはずがXと出てました。

http://orera.g.hatena.ne.jp/edvakf/20110809/1312910857

  • -
"123456789X".replace(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/, '$10');

みたいな置換をしたときに、「'X'」(10番目として解釈)になってほしいのか「'10'」(1番目+'0')になってほしいのかわからないよね、と言う話。
JavaScript で言えばどのブラウザでも「'X'」(10番目として解釈)になっていて、おそらく他の正規表現処理系でも同じなんじゃないかなと思います。
というわけで「1番目+'0'」と解釈させるにはどう書けばいいか、という視点で進めていきましょう。

JavaScript の場合

JavaScript の場合は replace の第2引数に function が書けますから、それを使うのが無難です*1

javascript:(function(){
  alert("123456789X".replace(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/, function($0,$1){
    return $1 + '0';
  }))
})() 


それから、第2引数が function じゃない場合の仕様をあたってみました。第2引数が function じゃない場合は文字列扱いし、その文字列の中の「$」の後に数字が続く時は以下のように置換されます。

Characters Replacement text
$n n を 1 桁の数字 1-9 、かつ $n に10進数が続かないものとして、 n 番目の捕捉。 n≤m かつ n 番目の捕捉が undefined ならば、代わりに空文字列を使用する。 n>m ならば、 結果は実装定義である。
$nn nn を 2 桁の数字 01-99 として、 nn 番目の捕捉。 nn≤m かつ nn 番目の補足が undefined ならば、代わりに空文字列を使用する。 nn>m ならば、 結果は実装定義である。

http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/15-5_String_Objects.html#section-15.5.4.11

これを読むと1桁扱いさせるには「$01」と表記すればよさそう…なんですが、数字が3桁以上続く場合にどうするかが規定されていません。(「2桁まで」と規定されている、とも読めますが。)

そういうわけで「$010」の扱いが Opera11 と他のブラウザで解釈が異なります。

javascript:(function(){
  alert("x23456789X".replace(/(.)(.)(.)(.)(.)(.)(.)(.)(.)(.)/, '$010'));
})()
// Opera11 => 'X'  (Opera10.10 => '$010')
// Other   => 'x0'

function で置換すれば解釈の違いは発生しません。

(function(){
  var chars = ['あ'.charCodeAt(0)];
  var regStr = ['(.)'];
  for (var i = 1; i < 82; i++) {
    chars[i] = chars[0] + i;
    regStr[i] = regStr[0];
  }
  var str = String.fromCharCode.apply(null, chars);
  var replaced = str.replace(new RegExp(regStr.join('')), function() {
    var m = arguments;
    return m[78] + '0';
  });
  alert(replaced);
  alert(str);
})()

Java の場合

Java の場合は単純に「\」(文字列リテラルで表現すると「\\」)でエスケープできます。String#replaceAll は最終的に Matcher#appendReplacement を呼びますから、その仕様に準じます。

置換文字列内でバックスラッシュ (\) とドル記号 ($) を使用すると、それをリテラル置換文字列として処理した場合とは結果が異なる場合があります。ドル記号は、先に説明したとおり、前方参照された部分シーケンスへの参照として処理される場合があり、バックスラッシュは置換文字列内のリテラル文字をエスケープするのに使用されます。

http://java.sun.com/javase/ja/6/docs/ja/api/java/util/regex/Matcher.html#appendReplacement(java.lang.StringBuffer, java.lang.String)

ソースを読む限り、何桁になっても大丈夫です。
また、Java の場合「$0」はマッチした部分文字列全体を指す(JavaScript での「$&」)という違いがあります。

PHP の場合

PHP は詳しくないんですが、@m61k さんに教えてもらいました。ありがとうございます。

例えば、\\11 は、 後方参照 \\1 の後にリテラル 1 が続くのか、後方参照 \\11 で その後には何も続かないのかが不明のため、 preg_replace() を混乱させる可能性があります。 この場合、解決策は、\${1}1 を使用することです。 こうすることで、1 はリテラルとなり、後方参照 $1 を明確に作成できます。

http://jp2.php.net/manual/ja/function.preg-replace.php