QA@IT

Rubyの後方参照とバックスラッシュを混ぜて使える?

2213 PV

Rubyでは以下のように\\1\\2などとすることで正規表現の一部分を後方参照できますよね。

ruby-1.9.3-p0 :001 > str = "abc"
 => "abc" 
ruby-1.9.3-p0 :002 > str.sub(/a(.)c/, "\\1")
 => "b" 
ruby-1.9.3-p0 :004 > str2 = "abbc"
 => "abbc" 
ruby-1.9.3-p0 :005 > str2.sub(/a(.*)c/, "\\1")
 => "bb" 

ところが、

ruby-1.9.3-p0 :006 > str2.sub(/a(.*)c/, "foo\\1")
 => "foobb"

と、#{}記法のようなinterpolationができるのに、

ruby-1.9.3-p0 :014 > puts str2.sub(/a(.*)c/, "\\\\1")
\1
 => nil 

となってしまします。期待する返り値は、\1 ではなく、\bb です。

ruby-1.9.3-p0 :013 > puts str2.sub(/a(.*)c/, "\\")
\
 => nil 

となるので、バックスラッシュ単体ではオッケーなので、エスケープしたバックスラッシュと後方参照をくっ付けるとダメということでしょうか。

何をやっているかというと、テキストデータの先頭にある#や##、###について、行頭にバックスラッシュを入れてエスケープしようとしているのでした。結局、str.match として MatchDataオブジェクトを返り値として受け取るようにして処理しました。

回答

置き換え文字列にバックスラッシュを含めるときは、
二重にエスケープが必要で読みづらくなりますし、
ブロックを渡して$1を使うのはいかがでしょうか。

http://rurema.clear-code.com/1.9.3/method/String/i/sub.html

1.9.3p194 :022 > str2.sub(/a(.*)c/){ "\#{$1}" }
=> "\bb" 

出力したいバックスラッシュが1つなら、この場合は1つ書けばよいです。

バックスラッシュに続けて部分文字列への参照を並べるとエスケープされてしまうので
期待通りの出力にならないのはその通りだと思います。

編集 履歴 (0)
  • やっぱりしょうがないんですね。ブロックに渡せばだいぶスッキリですね。正規表現ぽく書ける範囲に収まらないなら、いっそ$で始まる2文字系よりもMatchDataを操作するほうが私は好みですが。参考になりました! -
  • あ、これはダメな気が。str2.sub(/a(.*)c/){ "\#{$1}" } => "\#{$1}" となってしまい、バックスラッシュを2つ並べると出力にも2つバックスラッシュが含まれます。 -
  • あいや、バックスラッシュ2つが正解でした。文字列データに含まれる "\\" は出力時に \ になるのでした。 -

どこで解釈される \ なのか考えて順番に増やしていけばいくつ必要なのかがわかります。

この話では

  • \ + $1 に置き換えたい
  • sub の第2引数なので \ \ + \ 1 になる
  • 文字列リテラルに書くので \ \ \ \ + \ \ 1 になる
  • なので str2.sub(/a(.*)c/, "\ \ \ \ \ \ 1") と書けば良い

ということになります。
(Markdown 記法で解釈されて減ってしまうのを回避するためにスペースを入れていますが、
https://twitter.com/znz/status/205577507196518400 のようにスペースは不要です。)

編集 履歴 (0)
  • なるほど、こうやって慣れべてみると不可解なところはりませんね。スッキリと理解できました。ありがとうございます! -
ウォッチ

この質問への回答やコメントをメールでお知らせします。