書いた人:西山
Ruby には便利な標準添付ライブラリがたくさんありますが、なかなか知られていないのが現状です。そこで、この連載では Ruby の標準添付ライブラリを紹介していきます。
今回は、正規表現に関係するライブラリについて紹介します。 ということにして (連載タイトル上の建前) 正規表現の話をいろいろと書きます。 都合により次回にも続きます。
リファレンスや入門書などを見ればわかることは省略します。 基本的なことや詳しいことはリファレンスマニュアルの正規表現や「詳説 正規表現 第 2 版」などを参照してください。
今回は「\」によるエスケープ、アトム、優先順位、先読みと戻り読みの話です。
正規表現に限らず、エスケープがあるものを扱うときに意識しなければならないのは、そのエスケープは誰が解釈するのかと言うことです。
たとえば
というコマンドを実行したときに何が表示されるのか、そしてその理由が説明できますか?
筆者が試したところ、
が出力されました。
まず、「echo \\\\」部分です。bash や zsh の場合、
という流れになります。
cmd.exe の場合は
という流れになります。
次に「ruby -pe “gsub(/\\\\/,’[\\\\]’)”」の部分です。 これも同様に bash や zsh の場合、
という流れになります。 このプログラムに「\」が入力されて、置換が 1 回実行されて「[]」が出力されます。
cmd.exe と mswin32 版 ruby の場合は
という流れになります。 このプログラムに「\\\\」が入力されて、置換が 2 回実行されて「[\][\]」が出力されます。
cmd.exe と cygwin 版 ruby の場合は
という流れになります。 このプログラムに「\\\\」が入力されて、置換が 4 回実行されて「[][][][]」が出力されます。
このような点を理解した上で、String#gsub の説明や \ の影響 を読むと、慣れないうちはブロックを使う方が良い理由がわかると思います。
正規表現は Perl の正規表現の用語を使うと、アトムから成り立っています。
アトムには
などがあります。
文字そのものとは「a」や「あ」などの文字そのものにマッチするものです。
Ruby の正規表現のメタ文字には
という規則があります。 「\ を伴う英数字」は必ずメタ文字というわけではありませんが、将来メタ文字になる可能性があるので、現在メタ文字になっていないものは使わない方が良いでしょう。
メタ文字に含まれるのかどうかはわからないのですが、改行文字を意味する「\n」なども正規表現では使えます。
これらは「\」の話のところで書いたように誰が解釈するのかを知っておくと、 文字列から「Regexp.new」などで正規表現オブジェクトを生成するときに「\」がいくつ必要なのかを理解しやすくなります。
例えば「/\n/」というリテラルなら、Ruby のパース時には「\n」という内容の正規表現になり、 正規表現のコンパイル時に改行文字にマッチするアトムとして解釈されます。 「Regexp.new(“\n”)」でもマッチする対象は同じになるのですが、 文字列として改行文字になっていて、 正規表現のコンパイル時には「\」の解釈がないという違いがあります。 そして「/\n/」と同じ意味になるのは
などです。 この 3 つは文字列リテラルの書き方の違いだけで、「Regexp.new」の引数に渡るオブジェクトの内容は同じです。
参考として irb での実行例を載せておきます。 「Regexp.new(“\n”)」だけ違うことがわかると思います。
後方参照は、改行文字のように正規表現のコンパイル時でも意味が同じということはなく、 「\」の数を間違えると全く違う意味になるので注意が必要です。 例えば「Regexp.new(“\1”)」なら文字コード 1 の文字にマッチする正規表現になり、 「Regexp.new(“\1”)」なら後方参照する括弧がないので、単独では何にもマッチしない正規表現になり (「/(\d)#{Regexp.new(“\1”)}/」のように他の括弧を含む正規表現に埋め込むとマッチする)、 「Regexp.new(“(\d)\1”)」なら「00」などのように同じ数字が続くものにマッチする正規表現になります。
「/\01/」は inspect では「/\01/」になりますが、「/\001/」と同じ意味です。 0 から始まって 3 桁までの数字の場合は 8 進数での文字コードの指定になります。 4 桁以上の場合は、例えば「\0001」なら「\000」と「1」の意味になります。
「.」が普通は改行にはマッチしないことに注意が必要です。 改行もマッチさせるには「m」オプションを使って、 「/./m」のように正規表現リテラルを書くか、 「Regexp.new(‘.’, Regexp::MULTILINE)」のように「Regexp::MULTILINE」を指定した「Regexp.new」使うか、 「(?m:.)」のようにクロイスタと呼ばれる記法を使って「m」オプションを指定します。
「\ を伴う英数字」のうち幅のあるメタ文字の主なものは「\w」や「\D」のように文字クラスのような意味のものです。
幅 0 のメタ文字とは、正規表現のその位置が特定の条件にあっていればマッチするアトムです。
青木さんの添削にもありましたが、文字列先頭や文字列末尾の意味で「^」や「$」を使ってはいけません。Perl などの他の言語の正規表現とは意味が違うので気をつけてください。 たとえば、CGI の入力のチェックで間違って「^\d+$」のように使ってしまうと、「数字だけからなる文字列」を受け付けたつもりでも「数字のみの行」を含む文字列を受け付けることになってしまいます。
「\Z」も普通は使うことはないでしょう。「\A」とセットで文字列全体をチェックするのなら「\z」を使うべきです。「\Z」は「^」に対応する「$」のように「\A」に対応するものとして存在するだけで、普通は使うものではないと思います。行を意識して処理をしたいのなら、「\A」と「\Z」ではなく「^」と「$」が向いていることの方が多いはずです。
「\b」は文字クラスの中と外で意味が変わるメタ文字です。 文字クラスの中と外は正規表現の中と外のように別言語だと思った方が良いでしょう。 文字クラスの外の「\b」は「\w」と「\W」(または文字列の端) の間にマッチします。 たとえば「”abc.”.gsub(/\b/, ‘!’)」なら「!abc!.」になります。
「\B」は逆の意味になります。 たとえば「”abc.”.gsub(/\B/, ‘!’)」なら「a!b!c.!」になります。
「\G」は使用頻度が低いため説明は省略します。 用途によっては「\G」の代わりに strscan を使うことを検討してください。
文字クラスは「[]」内に列挙した文字にマッチするアトムです。
たとえば「(a | b | c | d | e | f)」と「[abcdef]」はほぼ同じ意味になります。 |
しかし、マッチしたいものが一文字とわかっているのなら、「 | 」による選択を使うよりも、文字クラスを使った方が無駄なバックトラックが発生しないため速いです。 |
文字クラスの使い方でよくある間違いとして「[foo]」や「[^(foo)]」のようなものがあります。 それぞれ
という意味になります。
文字クラスはうまく組み合わせるといろいろな表現が可能です。 例えば、
Perl の正規表現の優先順位は 4 つあります。 Ruby の正規表現の優先順位について明記されているものは見たことがないのですが、 同じと思って良いでしょう。
「 | 」による選択 |
一番優先順位の高い括弧とは「(〜)」や「(?:〜)」などです。 算術演算の括弧で「(1+2)*3」のように使うのと同じで、優先順位の低い選択をまとめたいときに「^(class|def)\s\w+$」の使えるように優先順位が高くなっているのだと思います。
次に繰り返しです。繰り返しはシーケンスよりも優先順位が高いので「\s\w+」の場合、「\s\w」の繰り返しではなく「\w」の繰り返しという意味になります。「\s\w」の繰り返しという意味にしたい場合は「(\s\w)+」のように括弧を使う必要があります。
次にシーケンスです。シーケンスとは「ab」のように「a」と「b」が並んでいるときに「a」の次に「b」があるという意味になることです。 数式で「ab」と書いてあれば「a×b」という意味なのと同じような感じだと思えば良いと思います。
最後に「 | 」による選択です。「^class | def\s\w+$」は選択がシーケンスよりも優先順位が低いため、「^class」または「def\s\w+$」という意味になります。 |
「^class\s\w+$」または「^def\s\w+$」とほぼ同じ意味にするには「^(class | def)\s\w+$」のように括弧を使う必要があります。 |
先読み (lookahead) は、幅 0 の正規表現です。 「^」や「$」と同じような「幅 0 の正規表現」ということをわかっておくことが重要です。
たとえば、左に「a」がない「b」という正規表現を書きたいとします。 このとき、先読みの意味を勘違いしていると「(?!a)b」と書いてしまうことになります。
ここでは「/(?!a)b/ =~ “abb”」という処理で考えてみます。 希望としては、左に「a」がない「b」ということで 3 文字目の「b」にマッチしてほしいはずです。 (以下の流れはあくまでも筆者のイメージなので、実際の実装がこうなっていることを確認したわけではありません。)
結果的に左に「a」があるかどうかに関係なく最初の「b」にマッチしてしまいました。
ここで本当に書きたかった正規表現は、次に説明する戻り読みがなければ簡単にはできません。
代わりに「(?:\A | [^a])b」のように文字列先頭または「a」以外の文字に続く「b」としてマッチさせて、一緒にマッチさせた「b」の前の文字は別途処理する方法などがあります。 |
先読みが有用な例として、Rubyリファレンスマニュアル - 正規表現の数字を 3 桁ずつコンマで区切るサンプルの方法 2 をあげておきます。
この例での処理の大まかな流れは
となると思います。
Ruby 1.8 以前では使えませんが、Ruby 1.9 以降では使えるようになっているため、戻り読み (lookbehind) も説明しておきます。
先ほどの例は戻り読みを使うと「(?<!a)b」と書くことが出来ます。
ここでは Ruby 1.9 を使って「/(?<!a)b/ =~ “abb”」という処理をすることを考えてみます。 (以下の流れはあくまでも筆者のイメージなので、実際の実装がこうなっていることを確認したわけではありません。)
括弧の中の正規表現「a」としてはマッチしなかったので、「(?<!a)」という正規表現としてはマッチしたことになります。
希望通り、左に「a」がない「b」ということで 3 文字目の「b」にマッチしました。
eregex というライブラリは [ruby-dev:1028] Regexp#operators からのスレッドで、 こういうものもライブラリとして作ることが出来るという例として作られたものです。 eregex.rb の中にも
と書いています。
どういうものかというと、eregex.rb の末尾の
のように Regexp の「 | 」(or) や「&」(and) のようなことが出来るようになります。 |
さらに and や or をとったりできない、 マッチ後の MatchData がどちらのマッチ結果になるのかわからないなど、 実用するには不向きな点があるので、 あくまでも例として見るのが良いのではないかと思います。
今の Ruby で正規表現の or に相当することがしたい場合は Regexp.union を使えば良いのではないかと思います。 Regexp.union は正規表現ならそのまま、文字列なら Regexp.quote して繋げてくれるため、 「Regexp.new(Regexp.quote(str))」の代わりに使うというのもありかもしれません。
参考として irb での簡単な実行例を載せておきます。
この前のるびま本読書会で正規表現が苦手な人が多そうだと思ったので、正規表現について書いてみました。 正規表現の優先順位については Effective Perl で知りました。 正規表現の優先順位についての説明は滅多に見かけないのですが、 参考になれば幸いです。
正規表現は言語ごとに細かい違いがあり、日本語での用語の統一もされてないようで、 「後方参照」といえば「back reference」の訳語として使われることが多い (Ruby のリファレンスマニュアルでもそうなっている) のですが、「lookbehind」の訳語として使われることもあるようです。 この記事の草稿の段階では lookbehind の訳語として後方参照と書いていたのを、途中で気付いて直したりしました。
西山和広。 最近は自己紹介でこの連載を書いてます、と言っています。 Ruby hotlinks 五月雨版のメンテナンスもたまにやってます。
Ruby リファレンスマニュアル刷新計画は一番人手が必要そうな第 3 段階が進行中なので、るりま Wiki を参考にしてお手伝いをお願いします。