Rubyでコマンドの中身を作るまで
最近、コマンドばかり作っていて、そんなにコマンド作ってどうするの?と思われそうだが、さらにどんどん作る。今度はRubyで作ってみる。
関連日記
作業環境
- MacBook OSX 10.6.2
- Developer Toolsをインストール済
- ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]
基本
- シェルスクリプトと同じように、行頭に#!解釈してもらうRubyインタプリタのパスを指定する。
- コマンド引数は、ARGVという変数に配列として渡される。(ARGV = $*)
- コマンド自体は、グローバル変数 $0 に渡される。(シェルスクリプトと違って、ARGV[0]はコマンドではない)
#!/usr/bin/ruby puts $0 ARGV.each {|arg| puts "hello, #{arg}!"} # ARGV = $* # 故に、以下と同等 # $*.each {|arg| puts "hello, #{arg}!"}
- 上記スクリプトを~/Desktiop/hello.rbとして保存した。
$ cd ~/Desktop $ chmod +x hello.rb $ ./hello.rb "山田 太郎" "伊藤 四朗" "鈴木 一郎" ./hello.rb hello, 山田 太郎! hello, 伊藤 四朗! hello, 鈴木 一郎!
なるほど!
オプションの解析
- OptionParser(以下のページが大変参考になりました。感謝です!)
#!/usr/bin/ruby require 'optparse' option_hash = {} OptionParser.new{|opt| opt.on('-a') {|v| option_hash[:a] = v} # オプション引数 なし opt.on('-b VAL') {|v| option_hash[:b] = v} # オプション引数 必要(VALは好みのテキスト) opt.on('-c [VAL]') {|v| option_hash[:c] = v } # オプション引数 任意([]で囲むと任意となる) opt.parse!(ARGV) } p option_hash puts $0 ARGV.each {|arg| puts "hello, #{arg}!"} # ARGV = $* # 故に、以下と同等 # $*.each {|arg| puts "hello, #{arg}!"}
- 素晴らしいシンプルさで表現できる。
$ ./hello.rb -ac -b 100 "山田 太郎" "伊藤 四朗" "鈴木 一郎" {:a=>true, :b=>"100", :c=>nil} ./hello.rb hello, 山田 太郎! hello, 伊藤 四朗! hello, 鈴木 一郎!
- オプション部分は解析され、すべてoption_hashに代入された。{:a=>true, :b=>"100", :c=>nil}
- ARGVには、オプション以外の引数のみが残っている。
- 基本で書いたコードを全く変更せずに、OptionParserの追記によって完全にパースされているところが好き。
これがRubyでコマンドを作る雛形になる。
RubyからCocoaを使う(sound.rbコマンドの実装)
- なんと、今時はObjective-Cを利用するまでもなく、RubyからCocoaにアクセスできてしまう*1のであった。
- しかも、OSX標準としてRubyCocoaがインストールされているのだ。(もちろん、Rubyも)
- C言語と違って、使い方は至って簡単。変数の型とか、メモリ管理とか、気にしないでおおらかに書けるところがいい。
- 仕様はObjective-Cでコマンドの中身を作るまでのsoundコマンドとほぼ同じ。比較してみると面白かも。
#!/usr/bin/ruby require 'optparse' require 'osx/cocoa' option_hash = {:l=>1} OptionParser.new{|opt| opt.on('-l times') {|v| option_hash[:l] = v.to_i} opt.on('-v') {|v| option_hash[:v] = v} opt.parse!(ARGV) } p option_hash if option_hash[:v] sound = OSX::NSSound.soundNamed(ARGV[0]) || OSX::NSSound.alloc.initWithContentsOfFile_byReference(ARGV[0], true) (option_hash[:l]).times do |i| sound.setCurrentTime(0) result = sound.play puts "#{ARGV[0]} (#{i}) playing..." if option_hash[:v] && result while(sound.isPlaying); sleep 0.2; end puts "#{ARGV[0]} (#{i}) stopped" if option_hash[:v] end
- ファイル名をsound.rbに変更して、試してみた。
$ ./sound.rb -vl 2 Blow {:v=>true, :l=>2} Blow (0) playing... Blow (0) stopped Blow (1) playing... Blow (1) stopped $ ./sound.rb -l 2 Blow $ ./sound.rb -v /System/Library/Sounds/Tink.aiff {:v=>true, :l=>1} /System/Library/Sounds/Tink.aiff (0) playing... /System/Library/Sounds/Tink.aiff (0) stopped
- いい感じに動いてくれている!
- コードの見通しの良さといい、メンテナンスの気軽さといい、久々でもなんとか使えてしまう緩さといい
Rubyはいい!
*1:中にはアクセスできないクラスもあるかもしれないが...。