Home / 別館 Home / Ruby
| 掲示板 | Mail |

Rubyショートショート


 このページは、僕が Ruby を使うためのメモ書きです。特に体系立てて Ruby の解説をしようという大それたことは考えていません。あくまでも僕の私的なメモです。基本的に今の僕は、Ruby を CGI の制作に使いたいと思っているので、CGI 方面の記述が多くなると思います。

 また、できる限り記述は正確にしたいと思っていますが、僕の理解不足で、間違った記述がある可能性があります。間違いに気づかれた方は、がらくた箱サポート掲示板メールでご連絡いただけると、すごく嬉しいです。


目次

基本 応用
  1. 入力と出力
    1. まずはお決まりの「Hello World !」
    2. フォームからデータを取得する
    3. ファイルからデータを取得する
    4. ファイルにデータを出力する
    5. キーボードからデータを取得する
  2. テスト環境
    1. とりあえず基本的なデバッグ
    2. RubyUnit の基本的な使い方
    3. CGIのエラーを捕捉する方法
    4. eRuby形式のエラーを補足する方法
  3. CGI 作成時の注意点
    1. ディレクトリの指定
    2. フォームの値を取得する
  4. Ruby の変数
    1. 基本
    2. オブジェクト指向と変数のスコープ
  5. テキストファイル処理
    1. タブをスペースに変換する
  6. 正規表現
    1. 基本
    2. メタキャラクタ
    3. ちょっとした効率化について
    4. 厳密ではないけれど当面の要求は満たす正規表現の例
  7. メソッド
  8. 配列
    1. Tips
    2. 注意点
  9. exerb関連
    1. レシピファイルの自動生成


出力と入力

まずはお決まりの「Hello World!」

 何はともあれ、しきたり(?)は守りましょう。と言うわけで、Ruby で HTML を吐き出す方法です。

 Ruby で HTML を書く方法としては、CGI ライブラリを利用せずに書き出す方法と、CGI ライブラリを使って書く方法の2つの方法があります。

CGIライブラリを使わずに

【コード】 実行例
#! /usr/local/bin/ruby -Ke
# CGIライブラリを使わずに「Hello World」を表示する方法

print "Content-Type: text/html; charset=EUC-JP¥n¥n"
    # "Content-Type: text/html¥n¥n"が最初に必ず必要

print "<HTML><HEAD><TITLE>Hello World! 1</TITLE></HEAD>¥n"
print "<BODY><P>Hello World!</P></BODY></HTML>"
    # HTML的に正しくしようとすると、上記のような記述になる
【出力結果】
Content-Type: text/html; charset=EUC-JP

<HTML><HEAD><TITLE>Hello World! 1</TITLE></HEAD>
<BODY><P>Hello World!</P></BODY></HTML>

 CGIライブラリを使わない場合、HTML 的に正しく書こうとすると、結構面倒くさい。

CGIライブラリを使った場合

【コード】 実行例
#! /usr/local/bin/ruby -Ke
# CGIライブラリを使って「Hello World」を表示する方法

require "cgi"

cgi = CGI.new("html4")

cgi.out('type' => 'text/html', 'charset' => 'EUC-JP') do
    cgi.html() do
        cgi.head{cgi.title{"Hello World! 2"}} +
        cgi.body{cgi.p{"Hello World!"}}
    end
end
【出力結果】 (画面の関係上、一部整形済み)
Content-Type: text/html; charset=EUC-JP
Content-Length: 177
Content-Language: ja

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<HTML><HEAD><TITLE>Hello World! 2</TITLE></HEAD>
<BODY><P>Hello World!</P></BODY></HTML>

 CGIライブラリを使った場合でも、それはそれで面倒なことになりそう。HTML 的には正しく出力されるのだけど、少なくとも、出力結果を凝ったレイアウトにしようと思った場合、HTMLドキュメントを書くような手軽さはなさそう。

 と言うわけで、状況が許す限り、出力には eRuby を使おうと思う。

補足:eruby コマンドが使えない環境で、eRuby を使う方法

 Xrea では 2002.04.06 現在、eruby コマンドがサポートされていないので、出力に eRuby を利用しようとすると、erb ライブラリを使わなければいけない。その時の基本的な記述方法。

【コード】 erb Light を利用して出力
require 'erb/erbl'    # erb Light を利用

print "Content-Type: text/html; charset=EUC-JP¥n¥n"
print ERbLight::new( filename.rhtml ).result(binding)
【コード】 erb Light を利用して出力(別解)
require 'erb/erbl'
require 'cgi'

cgi = CGI.new('html4')
cgi.out do
  ERbLight::new( filename.rhtml ).result(binding)
end


フォームからデータを取得する

【コード】 フォームのデータを取得する
require 'cgi'	#CGIライブラリを使用

cgi = CGI.new('html4')
str = cgi['form_name'][0]    # 配列として格納

 フォームの名前が予め決まっているときは、この方法で大丈夫。もし、インタフェースが決められないときは、こんな感じで書けばいい。

【コード】 フォームのデータを取得する(2)
require 'cgi'

cgi = CGI.new('html4')
forms = cgi.params    # ハッシュとして格納

 いずれの場合も、cgi['form_name'] は実際には、{'form_name' => ['value1', value2'...]} と配列で取得されていること。こうなっている理由はよく分かるのだけど、個人的には使いにくいので、こんなのを作ってみる。

【コード】 フォームのデータを取り出す
class Article
  def initialize( cgi_params )  # cgi.params を引数にとる
    @params = Hash.new
    cgi_params.each { |key, value|
      @params[key] = value.first
    }
  end
  
  def []( *args )
    @params[*args]
  end
end

 これで、article['form_name'] で、フォームからの値を取り出すことができる。



ファイルからデータを取得する

【コード】 ファイルの中身を一気に読み込む
data = open("filename","r"){|io| io.read}

 この方法だと、ファイルのデータを全部読み込んでしまうので注意が必要。

【コード】 ファイルの中身を読みながら処理する
open("filename","r"){|io|
 while line = io.gets
 end
}

 こっちの書き方の方が出番は多そう。



ファイルにデータを出力する

新規ファイルを作成する

【コード】 新規にファイルを作成
open("filename","w"){|file|
  file.write(# 書き込むデータ)
}

ファイルの末尾にデータを追加する

【コード】 ファイルの末尾にデータを追加する
open("filename","a"){|file|
  file.write(# 書き込むデータ)
}

ファイルの途中にデータを挿入する

 基本的な考え方としては、

  1. 別ファイルに挿入位置の手前までをコピーする
  2. 挿入するデータを別ファイルに追加する
  3. 挿入位置以降のデータを別ファイルにコピーする
  4. 別ファイルを元のファイルにリネームする

 こんな感じ。

【コード】 ファイルにデータを挿入する
File::open("tempFileName", "w") do |temp|
  File::open("baseFileName") do |base|
    line = base.gets
    if line == key # 挿入位置の条件
      temp.write( # 挿入データ )
    else
      temp.write(line)
    end
  end
end
File::rename("tempFileName", "baseFileName")


キーボードからデータを取得する

【コード】 スクリプトの実行中にキーボードからデータを取得
str = gets.chomp

 「str = gets」だと、改行まで取り込んでしまうので、ちょっと注意が必要。



テスト環境

とりあえず基本的なデバッグ

【コマンドラインから】
ruby -c target_script

 これで文法チェックが行える。タイプミスやカッコの閉じ忘れなどのケアレスミスは、結構これで見付けられる。っていうか、ケアレスミス多すぎ > 自分



RubyUnit の基本的な使い方

Testスクリプトの自動生成

【コマンドラインから】
c2t.rb class_name > test_script.rb
【出力結果を加工】
require 'runit/testcase'
require 'runit/cui/testrunner'

require 'hogehoge' # テスト対象のスクリプト

class Testclss < RUNIT::TestCase
 ※ここにテストを記述する。
end

if $0 == __FILE__
  if ARGV.size == 0
    suite = Testclss.suite
  else
    suite = RUNIT::TestSuite.new
    ARGV.each do |testmethod|
      suite.add_test(Testclss.new(testmethod))
    end
  end
  RUNIT::CUI::TestRunner.run(suite)
end


CGIのエラーを捕捉する方法

 Rubyで作ったCGIがエラーを起こした場合に、どういうエラーが起こっているのかを確認するための方法は、下記の通り。

【コード】 エラーを表示する
def error_out()
  print "Content-Type:text/html¥n¥n"
  $@.each {|x| print CGI.escapeHTML(x), "<BR>¥n"}
end

if __FILE__ == $0
  begin
    # Main の処理
  rescue StandardError
    error_out()
  rescue ScriptError
    error_out
  end
end
参考: 堀川久 『Ruby de CGI』 (オーム社開発局、2002年)


eRuby形式のエラーを補足する方法

 eRuby を使った場合に、どういうエラーが起こっているのかを確認するための方法は、下記の通り。

【コマンドラインから】
erb -x target_file > check_target_file.rb
ruby -wc check_target_file.rb

 やっていることは、一端、普通の Ruby スクリプトに変換して、チェックするという感じ。



CGI 作成時の注意点

ディレクトリの指定

 CGIを呼び出した時のディレクトリ位置と、実際に CGI が動いているディレクトリは異なる。当たり前のことだけど、CGI でファイルを操作する際には、ちょっと気をつけた方がいい。
 特に、CGIスクリプトで動的にコンテンツを生成する場合、サイト内リンクやイメージファイルの指定関係には気をつけた方がいい。不安な場合は絶対パスで指定した方が吉かも。

 こちらも当たり前と言えば当たり前だけど、URL で表示されるディレクトリのパスと、サーバ内の本当のディレクトリのパスは異なる。



フォームの値を取得する

 CGI ライブラリを使ってフォームの値を取得する場合、フォーム名が重複していない場合でも、配列になっているので注意が必要。

 フォームから取得した値に対してを処理を行う場合、cgi["form_name"][0]を使うか、cgi["form_name"].first を使うこと。String メソッドにあって、Array メソッドにないメソッドを使うときに、思わぬエラーが発生する可能性がある。

 これについて、とりあえず僕の回避策は、「フォームからデータを取得する」に記載されているとおり。



Ruby の変数

基本

 Ruby での変数の扱いについては、Ruby本で以下のように書かれています。

変数とはオブジェクトを指し示すもので、オブジェクトに付ける名札のようなものです。(中略)Rubyには他の言語で時々見られるようなオブジェクト(またはデータ)そのものを格納する容器としての変数は存在しません。代入は同じオブジェクトへの参照を発生させるだけで、オブジェクトをコピーしないことに注目してください。

 このことは、例えば下記のようなスクリプトの場合

【コード】 変数基本型その1
a = "foo"
b = a
a.upcase!
p a, b

 この変数「a」と「b」は、「FOO」となることを意味します。これ、OKです。

 ところで、次のようなスクリプトの場合、

【コード】 変数基本型その2
a = "foo"
b = a
a = "FOO" 
p a, b

 このとき、「a」は「FOO」なのですが、「b」は「foo」のままです。これちょっと注意です。

 考え方としては、「b」は「a」を参照しているのではなく、あくまでも「foo」というオブジェクトを参照していると考えればいいのでしょうけれど、僕自身はちょっと混乱してしまいます。

 ちなみに、

【コード】 変数基本型その3
a = "foo"
b = a
b.upcase!
p a,b

 僕の期待としては、「a」には元の「foo」を残して、「b」のみが「FOO」となって欲しいわけですが、これだと変数「a」「b」ともに「FOO」となります。これもかなり注意です。この例だと、

【コード】 変数基本型その3改
a = "foo"
b = a.upcase
p a,b

 これで、新しいオブジェクトを生成させることで、元のオブジェクトを保持できます。ただし、これがクラスのインスタンスを参照することになるとちょっと面倒そうです。

【コード】 インスタンスを参照する
class Foo
  attr_reader :num
  def initialize( num )
    @num = num
  end
  
  def plus( num )
    @num = @num + num
  end
end

a = Foo.new(1)
b = a
p a.num,b.num  # a = 1, b = 1

a.plus(1)
p a.num,b.num  # a = 2, b = 2

b.plus(1)
p a.num,b.num  # a = 3, b = 3

 配列なんかの場合は、さらに注意が必要で、

【コード】 配列を参照する
a = [0, 1, 2]
b = a
a[0] = nil
p a, b

 「a」「b」ともに、[nil, 1, 2] になります。[0, 1, 2] の状態を保持しておきたい場合は、「clone」とか「dup」とかを使う必要がありそう。



オブジェクト指向と変数のスコープ

 Ruby では、変数は「オブジェクトを指し示すもの」になっています。このことは、変数のスコープを考える上でも、注意しておく必要があります。例えば、次のような例の場合

【コード】 スコープの例
class Foo
  def initialize( n )
    @foo = n
  end
  
  def foo
    @foo
  end
  
  def sample1
    @foo +=1
  end
end

a = 1
b = Foo.new(a)
b.sample1
p a, b

 この場合、a の値は「1」のままで、b.foo の値は「2」になります。これ、OKです。
 注意しなければいけないのは、

【コード】 スコープの例その2(破壊的メソッド)
class Foo
  def initialize( str )
    @foo = str
  end
  
  def foo()
    @foo
  end
  
  def sample2
    @foo.upcase!
  end
end

a = "abc"
b = Foo.new(a)
b.sample2
p a, b

 この場合、b.fooの値は「ABC」で、aの値も「ABC」になります。
 これも、Ruby における変数は、あくまでもオブジェクトを参照する名札だと理解していれば、「a」が参照していた「abc」という文字列オブジェクトそのものを「b」のメソッドで変更してしまった納得できるのですが、何かの値を変数という箱の中に入れているというメタファで理解していると、ちょっと混乱が起きそうです。
 「a」の値はそのままで、「b.foo」の値のみを「ABCにしたい場合は、こんな感じに書けばいいのかもしれない。

【コード】 スコープの例その3
class Foo
  def initialize( str )
    @foo = str
  end
  
  def foo()
    @foo
  end
  
  def sample2
    @foo = @foo.upcase
  end
end

a = "abc"
b = Foo.new(a)
b.sample2
p a, b


テキストファイル処理

タブをスペースに変換する

【コード】 タブをスペースに変換する
# USAGE : tab2space.rb [width] file...

space = " " * ARGV.shift.to_i
ARGF.each_line do |line|
  puts line.gsub(/¥t/, space)
end

 エラー処理は全くないけど、とりあえず個人用としてはこれで。

 コマンドラインの引数で指定されたファイルを処理するのは、ARGF.each を使えばいい。このとき、ファイル以外の引数を必要とするときは、ARGF の前に ARGV を使って引数を取得しておく必要があるみたい。例えば、

【コード】 タブをスペースに変換する(これは問題が発生する)
ARGF.each_line do |line|
  puts line.gsub(/¥t/, " " * ARGV.shift.to_i)
end

こういうコードだと、スペースの数を指定したつもりが、ファイルだと判断されてエラーになるみたい。



正規表現

基本

 Ruby で正規表現を使ったパターンマッチの書き方は、基本下記の通り。

【コード】 パターンマッチ
  /正規化された文字列/ =‾ 対象文字列

 ただし、下記の書き方も認められている。

【コード】 パターンマッチ
  対象文字列 =‾ /正規化された文字列/


メタキャラクタ

繰り返し

* 直前の表現の0回以上の繰り返し。
*? 直前の表現の0回以上の繰り返し。非欲張り型。
+ 直前の表現の1回以上の繰り返し。
+? 直前の表現の1回以上の繰り返し。非欲張り型。
{min, max} 直前の表現の min から max 以下の繰り返し。
{min, max}? 直前の表現の min から max 以下の繰り返し。非欲張り型。
? 直前の表現の0回または1回以上の繰り返し。
?? 直前の表現の0回または1回以上の繰り返し。非欲張り型。

 欲張り型は、できる限り長くマッチしようとする。他方、非欲張り型は、最低限のマッチでよしとする。って言われても、どんな振る舞いになるか、よくわからん。こんな感じになるみたい。

【コード】 欲張り型と非欲張り型の違い
str = "ababc"
puts str.gsub(/(ab)+/, "A")  # (1) 欲張り型
puts str.gsub(/(ab)+?/, "A") # (2) 非欲張り型

 (1)の場合、結果は「Ac」になるのに対して、(2)の場合、「AAc」になる。

 と言うことは、欲張り型の場合、最初の「ab」で止まらずに次の「ab」までが1つの範囲になるのに対して、非欲張り型の場合、最初の「ab」でひとまず OK にして、改めて次の「ab」を探しているということ?



ちょっとした効率化について

 正規表現を使うパターンの1つに、ファイルの中から特定の文字列を探す場合、下記のようなスクリプトを書きがち。

File::each_line(file_name) do |line|
  puts line if line =‾ /a*/
end

 でも、これよりも、

keyword = /a*/
File::each_line(file_name) do |line|
  puts line if keyword =‾ line
end

 こっちの方が効率がいいみたい。原因は、前者の例が、1行読み込むごとに Regexp クラスを呼び出すのに対して、後者は最初の1回のみだから。対象になるファイルのサイズが大きければ大きいほど、目に見えるほど実行速度に差が出る。



厳密ではないけれど当面の要求は満たす正規表現の例

メールアドレスへのマッチ

validMail = Regexp.new("^([a-zA-Z0-9_¥.-]+)@(([a-zA-Z0-9_-]+¥.)[a-zA-Z0-9]+$)")

 これだけでは、該当メールアカウントが存在するかどうか正確にはわからないけれど、気休め程度にはなるかも。

URIへのマッチ

validURI = Regexp.new("http[s]{0,1}://[¥(¥)%#!/0-9a-zA-Z_$@.&+-,¥'¥"*=;?:‾-]+")

 こちらも、必ずしも正確なURIに対応しているわけではないけれど、やっぱり気休め程度にはなるかも。



メソッド

 オブジェクト指向では、振る舞いと状態によって世界をモデル化する。
出典:ケント・ベック 『ケント・ベックのSmalltalkベストプラクティス・パターン』(ピアソン・エデュケーション 2003年)

 個人的には、メソッドの役割は、

  1. オブジェクトの状態を変化させること
  2. オブジェクトの状態を利用した処理を行い、新たなオブジェクトを生成すること

これに尽きるのかなと思っていますが、どうなんでしょうね?



配列

Tips

配列の要素に一致した場所を返す

array.index( array.find{ |item| item == keyword } )

配列の複数の値を取得しながらブロックを処理する

出典:Ruby リファレンスマニュアル
class Array
  def every(&block)
    arity = block.arity
    return self.each(&block) if arity <= 0

    i = 0
    while i < self.size
      yield(*self[i, arity])
      i += arity
    end
    self
  end
end


注意点

  1. push, pop は破壊的メソッド。


exerb関連

レシピファイルの自動生成から実行形式ファイルの生成まで

【コマンドラインから】
ruby -r exerb/mkexr <targetfile.rb>
exerb <targetfile.exr>


Home / 別館 Home / Ruby
| 掲示板 | Mail |

とみくら まさや(vzx01036@nifty.ne.jp) $ Date: 2002/02/20 $