まつもと ゆきひろ@トヨタケーラムです.

しばらく,中断していましたが,新チュートリアルを開始します.
題して『プログラムを作りながら学ぶruby』です.

第1回『まずはgrepから』
-- 
rubyはオブジェクト指向スクリプト言語です.このチュートリアル
ではrubyのいろいろな使い方をプログラムを作りながら学んでいき
ます.前提になる知識はできるだけ少ないようにするつもりですが,
分からない点があれば,(e-mailで)どんどん質問してください.

これから学ぶrubyは『オブジェクト指向スクリプト言語』です.
rubyはオブジェクト指向の便利さを取り込みつつ,日常的なプログ
ラムを簡単に書けるように設計されています.「オブジェクト指向」
なんて知らないから難しいなどと思う必要はありません.rubyは分
かることから使っていけるようになっています.

スクリプト言語の良く使われる分野はテキスト処理です.rubyの最
初の例題として,テキストの中から正規表現に該当する行を捜し出
すコマンド「grep」を実装してみましょう.

grepは以下のようなコマンドです.

  grep pattern file...

fileが省略された時には標準入力から行の検索を行います.

これをrubyで(簡単に)書くとこうなります.

  $pat = ARGV[0]; ARGV.shift
  while gets
    print if /#$pat/
  end

たった4行ですが,これでも立派なプログラムです.rubyはインタ
プリタですので,作ったプログラムをコンパイルなどのステップ無
しにいきなり実行できますし,引数で指定したファイルからの読み
込みや,正規表現を使った検索などいろいろな便利な機能が最初か
ら組み込まれていますので,プログラムの作成が「お手軽」です.

もし,同じプログラムを例えばCで書けば正規表現の検索部分を除
いても結構な分量になることでしょうし,よほどプログラムに長け
た人でない限り,完成までかなりかかりそうです.この「簡単さ」
がrubyの長所のひとつです.

では,ちょっと実行してみましょう.

 ruby grep0.rb ruby /usr/dict/words
 ruby

ちゃんと動作しているようです(良かった,良かった).

それでは,rubyのことが分かるように,このプログラムをもうちょっ
と詳しく見てみましょう.

     1	$pat = ARGV.shift
     2	while gets
     3	  print if /#$pat/
     4	end

1行目は引数からパターンを取り出して,変数$patに代入していま
す.rubyのコマンドライン引数はARGVという配列に入っていますの
で,その最初の要素を取り出しています.ARGV.shiftというのは,
配列の最初の要素を取り除く操作です.

2行目の「while gets」というのは,引数で指定されたファイルか
ら1行ずつ取り出すためのある種の決まり文句です.

3行目でパターンに合う行があれば,printしています./#$pat/と
いう表現は,読み込んで来た行と$patで表される正規表現の比較を
行うという意味です.

4行目の「end」は2行目の「while」と対応するものです.whileに
よる繰り返しはここまでであることを示しています.

なんとなく,動作が分かりましたか? プログラムが短いのは良いん
ですけど,少し読みにくい感じがしますね.実はrubyは書きやすい
ようにいろいろな省略を許しています.そのせいで,こんなに短く
記述できるわけですが,逆に読みにくくなるのは仕方が無いですね.
では,省略無しで同じプログラムを書いてみましょう.

     1	$pat = ARGV.shift
     2	while $_ = gets()
     3	  if $_ =~ /#$pat/ then print $_ end
     4	end

あんまり読みやすくなっていないような気もしますが,省略されて
いた「$_」という変数が表に出ることで動作が想像しやすくなった
かも知れません.

ではこのプログラムを更に詳しく説明しましょう.

1行目は同じですね.2行目はgetsが関数であることがはっきり分か
ります.getsは1行読み込んで来て,変数 $_ に代入します.また
ファイルの終りが来ると偽を返しますので,whileループが終了し
ます.

3行目はずいぶんみかけが変わっています.まず,ifの形式が変わっ
ています.rubyではifは二通りの形式をとります(意味は同じです).
前のプログラムで現れたような後置形式です.もうひとつはこのプ
ログラムで見られるような前置形式で,こちらはendで終ります.
ここには出て来ませんが,前置形式にはelse節という,条件が成立
しなかった時に実行する文を指定できます.後置形式ではこれはで
きません.

次にifの条件の部分(ifとthenの間)も変わっています.条件部分に
現れた正規表現式は,今回は変数 $_ との比較の省略と見なされま
す.=~ は文字列と正規表現を比較する演算子です.

最後にprintに引数が指定されています.printの省略された時には
変数 $_ の値を出力するようになっています.

4行目も前と同じですね.

では,このプログラムの実行速度を見てみましょう.

 % time ruby /tmp/grep0.rb ruby /usr/dict/words
 ruby
 5.89user 0.25system 0:06.17elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k
 0inputs+0outputs (64major+408minor)pagefaults 0swaps

この程度の作業にi486DX4 75MHzで6秒以上ってのはずいぶん遅い気
がしますね.普通のgrepと比較してみましょう.

 % time grep ruby /usr/dict/words
 0.03user 0.06system 0:00.13elapsed 68%CPU (0avgtext+0avgdata 0maxresident)k
 0inputs+0outputs (87major+135minor)pagefaults 0swaps

うーん,やはり遅いなあ.rubyが遅いのはある程度仕方の無い部分
もあります.rubyはインタプリタ言語ですから,コンパイル型の言
語で記述されたgrepのような専用プログラムと比較するとどうして
も遅くなります.とはいえ,いくらなんでも遅すぎる気がするので,
ちょっと高速化してみましょう.

以下のプログラムが高速化したgrep1.rbです.

     1	$pat = Regexp.compile(ARGV.shift); 
     2	while gets
     3	  print if $_ =~ $pat
     4	end

1行目と2行目が代わったところです.つまり,正規表現を毎回生成
するのをやめて(//はデフォルトでは毎回正規表現オブジェクトを
生成します),同じ正規表現を再利用するようにしました.これだ
けでずいぶん高速になります.

 % time ruby /tmp/grep1.rb ruby /usr/dict/words
 ruby
 2.26user 0.12system 0:02.39elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k
 0inputs+0outputs (63major+174minor)pagefaults 0swaps

大体実行時間が半分になりました.それでもまだまだgrepよりも遅
いですけどね.

では,この「遅い」grepになんの意味があるんでしょう? 確かにす
ごく簡単に作れましたけど,普通のgrepより何倍も遅いのでは使い
道が無いような気もします.

このプログラムの存在意義はこの4行がプログラムである点です.
つまり,その気になれば,簡単に機能を拡張できるわけです.Cで
記述された専用プログラムであるgrepではこうはいきません.せい
ぜいオプションで機能を若干変更できる程度です.

例として,マッチした部分を反転するgrepを作ってみましょう.

     1	st = "\033[7m"
     2	en = "\033[m"
     3	
     4	$pat = Regexp.compile(ARGV.shift); 
     5	while gets
     6	  if $_ =~ $pat
     7	    gsub! $pat, "#{st}&#{en}"
     8	    print
     9	  end
    10	end

ちょっとだけ複雑になりましたが,これだけでマッチした部分を反
転させることができるようになりました.自分の望む機能を簡単に
実現するための道具としてrubyはもってこいです.

少々実行速度が遅くても,さっとプログラムして,さっと実行させ
て結果を得れば,最終的にはずっと速く結果を得ることができるこ
とは多々あると思います.そういう場面こそがrubyをはじめとする
スクリプト言語のもっとも有効な場面です.実行速度が本当に問題
になるようなツールが必要とされる場合は,それなりに時間をかけ
て高速なプログラムを作れば良いわけです.

では,ここで学んだことをまとめておきましょう.

  * rubyはインタプリタである.
  * rubyでは割と複雑なことをするプログラムを簡単に作ることが
    できる.
  * rubyはCなどに比べて開発速度は速く,実行速度は遅い.