タイトルは「ルビーのゆくすえ」じゃなくて「ルビーのぎょうまつ」デス。
文字列末尾に改行がある場合と無い場合とでどのような違いがあるか注意しよう。
文字列 "abc\ndef"
と abc\ndef\n"
はいずれも二つの行からなる。このことは
"abc\ndef".each_line.to_a #=> ["abc\n", "def"]
"abc\ndef\n".each_line.to_a #=> ["abc\n", "def\n"]
によって分かる。とくに意外な点はない。
ところで,正規表現の $
というアンカーは,行末にマッチするものだよね。
この「行末」というのは,さきほど確認した「行」という部分文字列の「末尾」とは限らず,もし改行があった場合は改行の直前になる。
このことは
"abc\ndef".gsub(/$/, "!") #=> "abc!\ndef!"
によって確認できる。
ここまでは,正規表現使いにとって常識の範囲だ。
ところが,文字列末尾に改行があると,
"abc\ndef\n".gsub(/$/, "!") #=> "abc!\ndef!\n!"
のようになる。
ちょっと意外な感じがしないか。最後の改行のあとにもマッチするだって? 2行からなる文字列に行末が三つもあるわけ?
ある人はこう思うかもしれない:
文字列末尾に改行がある場合は,そのあとに「空行」があるとみなされるんじゃないの。
この見方が当たっていないことは,
"abc\ndef\n".gsub(/^/, "_") #=> "_abc\n_def\n"
によって分かる。もし空行があるとみなされるのなら,"_abc\n_def\n_"
になるはずだ。
結局,$
は「行末および文字列末尾にマッチする」と考えたほうがよさそうだ。
リファレンスマニュアルでは $
について
行末にマッチします。行末とは文字列の末尾もしくは改行の手前を意味します。
と書いてある。さきほど書いたのと表現は違うし,「行末」を違う意味で使っているし,each_line の振る舞いから見える「行」の概念との整合性が疑われるけど,実質同じことを言っている。
なお,以上は Ruby の正規表現がそういう仕様だということであって,他の正規表現ツールでは必ずしもこうではない。