日記 (2015 年 3 月 16 日)
Ruby で文字列の一部を別の文字列に置換したいときは、 だいたい String#gsub
や String#sub
を使うと思うんですが、 正規表現を使った置換で、 マッチした部分を取り出すのに組み込み変数を用いるのが気持ち悪く感じます。
string = "abc-defg-hijk-lm-n-opqrst-uvwx-yz"
string.gsub!(/\-(\w)/){"[" + $1 + "]"}
例えば、 こんな例です。
String#gsub
が実行された時点で、 組み込み変数の $1
が書き換えられているわけですが、 プログラム上では明示的に $1
を変更するような記述がないので、 気持ち悪いのです。
特に、 置換をいろいろとしていると、 $1
や $2
がどの正規表現とマッチした結果なのかが分からなくなってしまい、 混乱します。
本当はこんな風に書きたいわけです。
string = "abc-defg-hijk-lm-n-opqrst-uvwx-yz"
string.gsub!(/\-(\w)/){|match| "[" + match[1] + "]"}
こんな風に、 ブロックを渡すと引数に MatchData
オブジェクトが渡され、 マッチしたグループ部分を取り出すのには、 その引数を用いるようにしたいわけです。
これなら、 変数名を変えることでどの正規表現とマッチした結果が代入されているか分かりますし、 ミスも減るはずです。
しかし、 String#gsub
にブロックを渡したときに、 その引数に渡されるのは、 マッチした文字列全体なのです。
ということで何とかしたいわけですが、 いろいろ調べてみると、 別のことが分かりました。 上のプログラムは実は下のようにも書けるみたいです。
string = "abc-defg-hijk-lm-n-opqrst-uvwx-yz"
string.gsub!(/\-(\w)/, "[\\1]")
ブロックではなく第 2 引数で置換後の文字列を指定するとき、 上のような形式でマッチしたグループを取り出せるようなのです。 バックスラッシュが 2 つ必要なのには注意です。 ややこしいですね!
string = "Hello,\nWorld!\n"
p string.gsub(/\n/, "\n")
p string.gsub(/\n/, "\\n")
p string.gsub(/\n/, "\\\n")
p string.gsub(/\n/, "\\\\n")
p string.gsub(/\n/, '\n')
p string.gsub(/\n/, '\\n')
p string.gsub(/\n/, '\\\n')
p string.gsub(/\n/, '\\\\n')
さて、 上のプログラムの出力がどうなるか分かりますか?