rubyの正規表現の複数行モード

 きょうふと、自分がへんな思い込みをしていることに気がつきました。
 恥ずかしいったらないんだけど、もしかして同じことを思ってる人がいるかもしれないので書いておきます。


 どんな思い込みかというと...
改行を含む複数行の文字列に対してruby正規表現で検索置換をする時、改行文字で区切られた行の行頭「^」や行末「$」を使うには、m修飾子が必要だという誤解です。ワンライナーで書くと

ruby -e 'puts "aa\nbb\ncc\n".gsub(/^/, "M")'

の結果が

Maa
bb
cc

だと思ってた。

Maa
Mbb
Mcc

という結果を得るには

ruby -e 'puts "aa\nbb\ncc\n".gsub(/^/m, "M")'

と書かないといけないと思っていたんです。まじすいません。
 そういう誤解を醸成したのは、perlの複数行モードをイメージしていたからでしょう。perlでは文字列に含まれる改行文字の直前の行頭にマッチさせるにはm修飾子が必要なんだよね。上記の例でいうと

perl -e '$_ = "aa\nbb\ncc\n";s/^/M/mg; print $_;'

と書かなければいけません。と、とりあえず他人のせいにしておくモード^^


 じゃあ、rubyのm修飾子ってなんなの? と思う方もおられるでしょう。
 リファレンスマニュアルに書かれている通りですが、rubyのm修飾子は正規表現で「.」が改行を含むようになるオプションです。*1

ruby -e 'puts "<p>\nhogehoge\n</p>\n".sub(/^<p>(.+)<\/p>/m){$1}'

 つまり、行頭「^」や行末「$」の位置とはまるで関係ないんですな。m修飾子とは関係なく、文字列全体の先頭にマッチさせたければ「\A」を、最終にマッチさせたければ「\z」または「\Z」(文字列が改行で終っていればその改行の直前)を使います。


 もうひとつ生き恥を晒しておきますと、この思い込みに気がついたきっかけは、下記のようなスクリプトを書いてる途中に、なぜか2行目(あとで消そうと思ってた)がマッチしてしまっていたからです。じぇじぇ!

$_.sub!(/^(<h\d)/){"<!-- 見出し -->\n#{$1}"}
$_.sub!(/^<h1.*?>(.+)<\/h1>$/){"= #{$1}"}

 いままで、なぜ気がつかなかったのか、つくづく情けないことだなあ(詠嘆)。


参照

詳説 正規表現 第3版 p107「3.3.4.3 ドット全マッチモード(単一行モード)」、p.108 「3.3.4.4 拡張行アンカーマッチモード(複数行モード)」
Rubyist Magazine - 標準添付ライブラリ紹介 【第 14 回】 正規表現 (3)

*1:ちなみに(混乱するかもしれませんが)、同じ結果を得るためにはperlだとs修飾子(単一行モード)を使います。perl -e '$_ = "

\nhogehoge\n

\n";s/^

(.+)<\/p>/$1/s; print $_;'