[ruby-list:250] TUTORIAL(sono:1) - grep = first step
From:
matz@... (Yukihiro Matsumoto)
Date:
1996-03-26 12:22:12 UTC
List:
ruby-list #250
まつもと ゆきひろ@トヨタケーラムです.
しばらく,中断していましたが,新チュートリアルを開始します.
題して『プログラムを作りながら学ぶ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などに比べて開発速度は速く,実行速度は遅い.