subやgsubで括弧とマッチした部分の再利用、および式展開の方法

Rubyで文字列を置換するsub, gsubメソッドだが、元の文字列の一部を使用したいことはよくある。また、置換後の文字列に式展開を行いたいこともあるだろう。その方法が複数あるので紹介したい。

その前に sub, gsub の簡単な説明を

String#sub, String#gsub は文字列を置換するメソッドである。第1引数と一致する部分を第2引数に置換する。sub は最初に一致したものを置換し、gsubはマッチする全ての文字列を置換する。破壊的なメソッド sub!, gsub! も用意されている。

str = "a perl, a ruby, a python"
str.sub('a', 'the')             # => "the perl, a ruby, a python"
str.gsub('a', 'the')            # => "the perl, the ruby, the python"

第1引数には正規表現も利用できる。

str = "a perl, a ruby, a python"
str.sub(/\s/, '_')              # => "a_perl, a ruby, a python"
str.gsub(/\s/, '_')             # => "a_perl,_a_ruby,_a_python"

このように正規表現で置換元を指定する場合、括弧を使用すると置換後の文字列に利用することができる。今回紹介するのはその方法についてである。

シングルクォート内で \数字 を使う方法

シングルクォート内で「\数字」を使うと、n番目の括弧を置換後の文字列に使用できる。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/, '<p>\1</p>') # => "<p>ruby</p> <p>python</p>"

このとき、「#{}」では式展開できない。それどころか、改行文字などの特殊文字も使用できない。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/, '<p>#{1+2}</p>\n') # => "<p>\#{1+2}</p>\\n <p>\#{1+2}</p>\\n"

式展開したい場合、「String#%」を利用するのがいいだろう。いわゆるフォーマット文字列だ。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/, '<p>%i</p>%s' % [1+2, "\n"]) # => "<p>3</p>\n <p>3</p>\n"

ダブルクォート内で \\数字 を使う方法

ダブルクォート内で「\\数字」を使うことでも、n番目の括弧を置換後の文字列に使用できる。

そもそも、置換後の文字列内で括弧にマッチした部分を表す文字列は「\数字」なのだが、ダブルクォート内では「\1」は特殊文字「\x01」と解釈されてしまう。そこで、バックスラッシュでエスケープしたバックスラッシュ(\\)と数字で「\数字」を表現しなければならないのだ。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/, "<p>\\1</p>") # => "<p>ruby</p> <p>python</p>"

ダブルクォート内のため「#{}」での式展開も特殊文字も使用可能である。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/, "<p>#{1+2}</p>\n") # => "<p>3</p>\n <p>3</p>\n"

ブロックを使う方法

第2引数を用いずにマッチした部分をブロックの評価結果で置換することもできる。この場合、括弧とマッチした文字列を示す特殊変数 $1~$9 が使用できる。

"<b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/) {"<p>#{$1}</p>"} # => "<p>ruby</p> <p>python</p>"

注意

ダブルクォート内で $1 を使用すればいいのでは?と思うかもしれないが、残念ながらそれはできない。

"<u>perl</u> <b>ruby</b> <i>python</i>".gsub(/<.>(.+?)<\/.>/, "<p>#{$1}</p>\n") # => "<p></p>\n <p></p>\n <p></p>\n"
$1                              # => "python"

$1~$9 はマッチ後の参照であり、正規表現内扱いのgsubの引数では使用できないためだ。

まとめ

個人的には、式展開がない場合はシングルクォート、ある場合はブロックを使うのがオススメだ。ちなみに、シングルクォート内での「\\」は「\」と解釈されるが、ややこしいので使うのはやめておいた方がいい。