sedの代わりにRubyで複数行への正規表現マッチング

2017/02/27

行単位に処理するのであれば、sedで簡潔に正規表現を使用してマッチングができる。しかし、複数行にわたって正規表現のマッチングをするのであれば、Rubyを使った方がいい。複数行であっても、正規表現内に行数指定がないのであれば、Rubyでなくてもいいのだが。

まずは複数行であっても正規表現内に行数の指定がなく、Rubyである必要がないものをsedを使って書いて、それをRubyに書き直してみる。
題材として、Tomcatのserver.xmlからコメントアウトされた部分(<!--,-->)を省いて標準出力に表示してみることにする。

sedを使うと、次のようになる。

1つ目のsedで1行内にコメントアウトがある部分を消し、2つ目のsedでコメントアウトの開始タグと終了タグが複数行に別れている場合のその範囲を消している。最後のsedでは空行になった行を消して、見やすくしている。

これをRubyで何通りかに書き換えてみる。

この書き方は、File.readでserver.xmlファイルの全行を読み込んで、その文字列をgsubで正規表現による置換にかけている。正規表現にmオプションをつけており、.が改行にもマッチするようになっている。.が改行にマッチするので、<!--.*?-->はコメントアウトタグに囲まれており、その囲いの中は改行を含む任意の文字の繰り返しの最短マッチとなる。ちなみに?の部分が最短マッチを示している。正規表現のデフォルトは最長マッチになっているので注意が必要。

正規表現自体には関係ないが、Rubyのワンライナーの練習のために、ファイルの読み込み方を変えて書いてみる。

先ほどの書き方も再掲して、計5通りの書き方をしている。
1つ目はFile.readでファイルを全行読み込み。
2つ目はFile.readで読み込む点は同じであるものの、Rubyワンライナー自体の引数としてファイルを渡している。ワンライナーではなくスクリプトファイルにRubyを書く場合にはこちらの方が引数としてファイル名を受け取れるので便利。
3つ目はARGF(スクリプトに指定した引数をファイル名とみなして、それらのファイルを連結した 1 つの仮想ファイルを表すオブジェクトで、ARGV が空なら標準入力を対象とする)という仮想ファイルオブジェクトのreadで読み込んでいる。
4つ目はcatをパイプでつないで標準入力をRubyで使用している。引数がないので、ARGFは標準入力が対象になる。
5つ目はcatをパイプでつないで標準入力をRubyで使用している。readlinesで標準入力全行を各行の配列として取得できるので、joinで配列を1つの文字列につなぎ合わせている。
3つ目か4つ目のARGFを使う書き方が素直な書き方だと思う。

正規表現自体を別の書き方に変えてみる。先ほどはmオプションを使い、.が改行にマッチするようにして複数行に渡る正規表現のマッチングを行ったが、今度はmオプションを使わずに実現する。

mオプションをつけることで、.が改行を「含む」任意の文字になったので、mオプションをつけないのならば、改行を「含む」という点をor(|)で論理的に実現すればいい。(.*?|\n)*は任意の文字列の最短マッチもしくは改行となり、コメントアウトの閉じタグが出現するまでどんな文字が来てもいいし何度改行してもよくなる。

(.*?|\n)*を少し書き直してみる。

(.*\n)*?.*は任意の文字が0個以上あり改行がその次に来るというパターン((.*\n))が最短マッチで0個以上あり(*?)、その後コメントアウトの閉じタグに出会うまで任意の文字が0個以上続くという正規表現になる。

sedとRubyを使ってserver.xmlからコメントアウト部分を除外したが、実際にどのようになっているのかdiffで確認してみる。

ここまでみると、sedの方がRubyより書き方が簡潔に思えるし、実際、今回のようなお題であれば、Linux系であればどこでも使えるsedの方を使うべきだろう。しかし、単なる複数行への正規表現マッチングではなく、行数の概念が出てくるとsedは途端に厳しくなり、Rubyが輝き出す。

例えばある文字列を含む行の2行下に別のある文字列が登場した時だけマッチングさせたい場合、sedでもできなくはないが複雑になってしまう。これはsedが行単位で処理を行う行志向の言語で、改行を扱うことをあまり考慮していないからである。
ちなみにsedで実現する方法はsedやgrepで、ある行のn行前から処理対象にするで書いた考え方を応用すればできなくもない。
行志向ではない汎用のプログラミング言語であるRubyであれば、改行を普通に扱うことができる。

このワンライナーでは、「<?xml」の後に、任意の文字列が0個以上先行する改行が2つあり、その後行中に「Licensed」という文字列がくるという正規表現をマッチングさせている。
2行下というように行数の概念が出て来るとRubyを使うべきと言える。

-Linux, Ruby
-, ,