■ Ruby の便利なところ

ファイルのレイアウトを変更中です。 この部分の URL は変更されるかも...

irb

Ruby をちょっと試すときにとても便利なスクリプトがあります。 このスクリプトを使うと、対話的に Ruby を試すことができます。 次の例は実際に irb を使ってみたところです。

$ irb
irb(main):001:0> a = "a"
"a"
irb(main):002:0> b = a
"a"
irb(main):003:0> a.sub!(/a/, 'b')
"b"
irb(main):004:0> p a
"b"
nil
irb(main):005:0> p b
"b"
nil
irb(main):006:0> p a.id
358786774
nil
irb(main):007:0> p b.id
358786774
nil
irb(main):008:0> 

スクリプトを書いているときに動作の確認を実施するということはよくあると思います。 このとき、これらを利用するととてもいいですよ!

■ 変数のスコープ

Ruby の変数は、先頭の文字によりスコープが決まります。 普段するような「小文字」ではじまる変数はローカル変数です。 グローバル変数を定義したい場合は、「$」ではじめます。 インスタンス変数は「@」ではじめます。 変数ではなく定数なのですが、定数は「大文字」ではじめます。

Ruby では、普通に変数を使うと、それがローカル変数になります。 これはとてもうれしいことです。 有効範囲(スコープ)が限定されているため、グローバル変数が持ち込む問題を引きずらなくて済みます。

変数を使用する場合には、先に変数に「値」を入れて初期化することが必要です。 スクリプト言語では、値を設定せずに「参照」できてしまうことが多いですが、Ruby はしっかり値を設定することが必要です。 注意ください。 このおかげで、初期化されていないために起きる問題を回避することができます。

■ 引数

コマンドライン上で渡される引数を処理できると、作成したスクリプトの幅が広がります。 ちょっとしたオプションを加えるとか、ファイルを複数扱えるようになります。

単純に引数として渡された文字列を表示するだけのスクリプトです。 引数は「配列 ARGV」に入っています。 最初は for を使った例。

for i in ARGV
  print i, "\n"
end

exit
次はイテレータ。
ARGV.each {|arg|
  print arg, "\n"
}

exit
その次は配列 ARGV から一つ一つ値をとりだしてなくなるまで処理する例です。
while arg = ARGV.shift
  print arg, "\n"
end

exit
イテレータのもう一つの書き方として「do」を使った例です。
ARGV.each do |i|
  print i, "\n"
end

exit

注意事項があります。 引数(配列 ARGV)にオプション(実行時の動作を指定する意味)とファイル名が混在している場合には、オプションを確認後「配列 ARGV」から取り除くことが必要になります。 スクリプトの書き方に依存しますが、Ruby の便利な機能(「ファイル」参照ください)を使う場合は必要な処理です。 このため、引数としてオプションのあとにファイル名がくるような場合にはつぎのように書くことになると思います。

while arg = ARGV[0] and /^-/ =~ arg
  ARGV.shift
  print "option ", arg, "\n"
end

for file in ARGV
  print "file ", file, "\n"
end

exit
実行例は次のようになります。
$ ruby arg1.rb -a -b -c a b c d e f
option -a
option -b
option -c
file a
file b
file c
file d
file e
file f

■ ファイル

引数のファイルをそのまま出力する UNIX の cat というコマンドと同じことをするには、つぎのように書きます。

while gets
  print
end
exit
入力したファイルを何らかの処理をする場合の決まりパターンです。

gets は、変数「$_」にファイルを一行読んだ結果が入ります。 while のループ内で、この値を処理するように書きます。 この例では、「print」(引数がないときは「$_」を出力)で出力するだけの処理を行っています。

省略形を使わない場合は、こんな感じの書き方になります。 引数が入っている配列 ARGVfor で処理する例です。

for filename in ARGV
  file = File.open(filename)

  while oneline = file.gets
    print oneline
  end

  file.close
end

exit
イテレータを使った例になります。
ARGV.each {|filename|
  file = File.open(filename)

  while oneline = file.gets
    print oneline
  end

  file.close
}

exit

ARGF

while gets
  print
end
exit
で処理している場合、処理中のファイル名などが必要になります。 このとき ARGF (または $<) に情報が入っています。 次の例は、入力した行の前にファイル名を付け加えるものです。
while gets
  print ARGF.filename, ':', $_
end

exit

■ 文字操作

Ruby は文字操作がとても便利です。 特定の文字は、配列表記で次のようにするととりだせます。

$ irb
irb(main):001:0> a = "strings"
"strings"
irb(main):002:0> p a[0,1]
"s"
nil
irb(main):003:0> p a[-1,1]
"s"
nil
irb(main):004:0> p a[-1,2]
"s"
nil
irb(main):005:0> p a[0,2]
"st"
nil
irb(main):006:0> p a[-2,2]
"gs"
nil
irb(main):007:0> 

■ 文字列操作

Ruby の場合、文字列操作がとても便利です。 マニュアルの「String クラス」を参考にしていろいろ試してみてください。

● タブの展開

98/9/26

タブ文字を表示されるのと同じようにスペースに展開したいときはありませんか? Perl の FAQ を参考に次のように書いてみました。

class String
# perlfaq4.html#Data_Strings
#  1 while $string =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
  def tab_expand
    1 while self.sub!(/\t+/) { ' ' * ($&.size * 8 - $`.size % 8) }
    self
  end
end
対象が文字列なので、「String クラス」にしています。

ちょっと余談ですが、この手の小さな便利ものはいつでも require して使えるようにしておくといいですね。 私の場合には、環境変数 RUBYLIB に個人のディレクトリを追加(システムの部分も忘れずに!)しています。 この登録したディレクトリに、ある程度汎用で使えるものを登録しはじめています。

■ 配列 Array クラス

2001/11/5

Ruby の場合、配列 Array クラスがとてもよくできています。 マニュアルの「Array クラス」を参考にしていろいろ試してみてください。 あと「Enumerable」もいっしょに参考にしてください。 便利ですよ。

● 配列の要素を全部修正

2001/11/5

数値を入れた配列があった場合、すべての値に 10 を加えたい場合はどうしますか? このような場合に Array#collect がとっても便利です。

$ irb
irb(main):001:0> arr = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
irb(main):002:0> arr.collect {|x| x += 10}
[11, 12, 13, 14, 15]
irb(main):003:0> 

配列の要素それぞれに、なんらかの操作を行ないたいときには Array#collect です。

● 配列に要素があるか?

2001/11/5

Array クラスでは、配列の要素に一致したものがあるか? という判定には Array#include? が使用できます。 これをブロックを評価した結果行なえるのが Array#find です。 実は Array クラスで include している Enumerable で定義されています。 each が定義されていれば、他のクラスでも使えちゃいます。

例をみてみましょう。 どちらも同じ要素をみつけることを行なっています。

$ irb
irb(main):001:0> arr = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
irb(main):002:0> arr.include?(3)
true
irb(main):003:0> arr.find {|i| i == 3}
3
irb(main):004:0> 
Enumerable#find では、条件が成り立てばいいので、Array#include? のように即値(そのものの値)ではなくても使えちゃうのが便利ですね。 「5 以上の値があるか?」とか。
$ irb
irb(main):001:0> arr = [1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6]
irb(main):002:0> arr.sort.find {|i| i > 4}
5
irb(main):003:0> arr.sort.reverse.find {|i| i > 4}
6
irb(main):004:0> 
Array#find は、Array#each を使って実装されています。 Array クラスの要素の順番通りに評価していきます。 もし、順番に逆順(後ろから)にしたい場合は Array#reverse などを使ってください。

■ 変数 $_

変数「$_」は、省略された場合のデフォルト変数として使われることがあります。 Ruby の場合は、メソッド/関数形式の場合、次の処理で利用されています。 また、文字列の比較などのデフォルトの対象になります。 何でもかんでも「$_」をデフォルト変数/引数として使用するというわけではありません。

gets
readline
print
split
scan
chop
chop!
chomp
chomp!
gsub
gsub!
sub
sub!
また、「$_」はローカル変数です。 このためいつのまにか値が変わってしまっているというような問題も発生しません。 手軽な処理を記述する「手助け変数」となっています。

sub, gsub については、ちょっと注意が必要です。 オブジェクトを省略した形の sub, gsub は、対象を $_ に格納されているオブジェクトになります。 このとき、sub!, gsub! でなくとも、結果として $_ が示している内容が「入れ替る」ことが発生します。 動作としては次のようになります。 sub, gsub では、最初に $_ が示しているオブジェクトのコピーを作成します(dup, clone)。 このコピーを対象に sub, gsub が実施されます。 実施後 $_ が示しているオブジェクトはコピーで作成され修正されたオブジェクトになります。 sub!, gsub! の場合には、対象オブジェクトそのものを修正しますので動作としては違います。 ただ、変数 $_ を対象にした場合には、みかけの動きが sub, sub! で同じようになるので注意ください。

while gets
  gsub(/ruby/, 'Ruby')
  print
end

exit
というスクリプトは、ちゃんと動作します。

■ 変数の値の入れ替え

Ruby では、変数 ab の値を入れ替えるのはつぎのように書きます。

a, b = b, a
わかりやすいし、簡単でいいですね。

一般の言語では、テンポラリ変数を使用したりします。

work = a;
a = b;
b = work;

■ エラー出力

画面にメッセージを出力する場合、print を使用します。 このとき、STDERR.print とすることで、標準エラー出力に出力できます。 明示的に「エラー」のメッセージを表示したい場合などに有効です。

UNIX 系の文化では、パイプを使用して、処理をいくつかのコマンドに渡すことがあります。 このような場合、エラーメッセージを標準出力に出力してしまうと、次の処理を行う(フィルタ)プログラムにエラーメッセージまで渡してしまいます。 標準エラー出力は、基本的にはプログラム実施しているターミナル(端末)に直接メッセージを出力するのでこのようなことが起きません。

また、エラーの出力先を Ruby のスクリプト全体でコントロールしたい場合などには $stderr.print とすることで制御可能です。 $stderr の出力先をエラーログファイルにしておくことで、エラーログをとることになります。

エラーメッセージなどの扱いは、統一しておくと拡張時などの管理が楽になります。

■ 変数とオブジェクト

変数は使用する前に初期化が必要です。 参照する前に値を入れましょう。

数値number = 0
文字列string = ""
配列array = []
ハッシュhash = {}

Ruby での変数は、オブジェクトの入れ物です。 同じオブジェクトを入れている変数がある場合、一方の変数が扱っているオブジェクトを対象にした変更は、もう一方の変数から参照しているオブジェクトも変更してしまいます。 ちょっと注意が必要です。

次が例になります。 変数「a」に格納されいてる文字列の「12345」というオブジェクト(へのリファレンス)を変数「b」に入れます。 このとき、オブジェクトのコピーを入れるのではなくオブジェクトへのリファレンス(オブジェクトを示すもの)を変数「b」に入れます。 このため、変数「a」を対象にした変更は、変数「b」で参照しても変更されています。

$ irb
irb(main):001:0> a = "12345"
"12345"
irb(main):002:0> b = a
"12345"
irb(main):003:0> a.chop!
"1234"
irb(main):004:0> p b
"1234"
nil
irb(main):005:0> p a.id
358785744
nil
irb(main):006:0> p b.id
358785744
nil
irb(main):007:0> 
オブジェクトをコピーしたい場合は「dup メソッド」を使ってコピーを生成します。
$ irb
irb(main):001:0> a = "12345"
"12345"
irb(main):002:0> b = a.dup
"12345"
irb(main):003:0> a.chop!
"1234"
irb(main):004:0> p b
"12345"
nil
irb(main):005:0> p a.id
358785744
nil
irb(main):006:0> p b.id
358772164
nil
irb(main):007:0> 
dup は、オブジェクトのコピーを作成しますが、そのオブジェクトがリファレンス先を示している場合は、そのリファレンス先まではコピーしません。 このため、dup したインスタンス変数を使用して、気がつかずリファレンス先まで修正した場合、もとのインスタンス変数が示すオブジェクトまで変更されていることがあります。 ちょっとわかりにくいですが、注意してください。

■ 例外処理

Ruby は、問題があれば例外が発生します。 ということは、エラー処理の方法が根本的に楽です。 エラーが起きたらエラーを通知して終了することが必要です。 この処理を記述しなければ実施しない言語が多くあります。 このため、問題があってもエラー処理を書いていないため実際にエラーが起きたコードの部分で判断できず、後のコードで落ちたりします。 こういう問題は、C や Perl でプログラミングになれない方が実際よく引き起こしてしまいます。 エラー処理は重要なんですよ。

スクリプト言語としては、手軽に処理を実施できることが重要です。 必要なときにはエラーの対応を自分で例外処理をハンドリングするように記述すればいいです。 何も記述しない場合は、「例外を通知して終了する」という動作はプログラマの負担を軽くします。 基本的に何も記述しなくても、エラーが発生すれば「例外」が起きて、処理が終了してしまうのです。 とっても便利です。

ファイルをオープンする例を Perl と C と Ruby で比較してみます。 みなさん、ファイルなどをオープンするときは、必ずオープンできるとは限らないのでエラー処理が必要なんですよ。 でも、Ruby は特別な記述は必要ありませんけど。

Perl の場合は次のように記述します。

open(F, '/etc/hosts') || die "can not open \"/etc/hosts\": $!";

C の場合は次のように記述します。

#include 

FILE *fp;

if ((fp = fopen("/etc/hosts", "r")) == NULL) {
  perror("fopen(3): /etc/hosts");
  exit(1);
}

Ruby です。

f = File.open('/etc/hosts')

いかがでしょうか? 実際手軽に使いたいスクリプト言語では、エラー処理を記述しなくても適切に処理されるというのはありがたいです。 繰り返しになってしまいますが、C や Perl などをプログラミングになれていない方が使うと問題を抱え込むことが多いです。 エラーの対応は、エラー処理は、「重要」です。 この重要性に気が付かないためか、エラーを無視したコーディングがなされてしまいます。 なので、ちゃんとエラー処理を行うか Ruby を使いましょう!

■ ネットワークに接続

Ruby はネットワークに接続するのがとても楽になっています。 C や Perl では、ネットワークに接続するまでの手順がめんどうです。 Ruby はとてもよくカプセル化されているために、手軽に扱えます。 スクリプト言語としては理想的ですね。

マニュアルにも同じような記述がありますが、TCP で接続するサンプルです。 引数で渡した「サービス名」に接続します。 デフォルトで「finger」に接続します。 サービスについては、「/etc/services」に記述されているものがありますが、実際に使えるものは「netstat -a」コマンドでの出力されるものです。 また、inetd がサービスするもので「/etc/inetd.conf」に記述されているものです。

require "socket"

port = if ARGV.size > 0 then ARGV.shift else "finger" end
print port, "\n"

s = TCPsocket.open("localhost", port)

while gets
  s.write($_)
  if $_ = s.gets
    print
  else
    break
  end
end

s.close

exit
実行例です。
$ ./tcpc.rb finger
finger
tetsu
Login: tetsu                            Name: WATANABE Tetsuya
quit

find.rb の使い方

あるディレクトリ以下のファイル名の一覧利用したい場合、find.rb を利用します。 とても手軽に処理が書けます。 単純に指定したディレクトリ以下のファイル一覧は次のようになります。

#! /usr/local/bin/ruby
# /home/tetsu/src/ruby/ck/find.rb
# Created: June 04,1998 Thursday 21:31:09

require 'find'

dir = ARGV.shift || '.'

Find.find(dir) {|f|
  if File.directory? f and File.symlink? f
    Find.prune
  end
  print f, "\n"
}

exit
で、Tips なので「シンボリックリンクされたディレクトリ」を確認して、表示しないようにしています。 それが「Find.prune」です。 特定の条件のときに、呼びだすことで Find.find の処理をキャンセルできます。 ファイルのときには関係がないのですが、ディレクトリのときはそのディレクトリ以下の処理をキャンセルできます。 これを実施しておかないと、シンボリックリンクで結ばれたディレクトリがループしていた場合、表示が終わらないような状態になってしまいます。

ちなみに、find.rb を読んで、この使い方がすぐ思いつきませんでした。 シンボリックリンクされたディレクトリはキャンセルしたいし、どうしたものか? と悩んだものです。 使い方が分かるとあっけないですね。 とても記述が楽です。

■ バイナリファイルを扱う unpack

多くのスクリプト言語が、扱えるファイルの形式がテキストだけだったりしますが、Ruby はバイナリファイルも扱えます。 ちょっと変な例ですが、サンプルとして次のものがあります。

■ クラス定義の便利 to_s

何らかのクラスを定義します。 このときクラスの情報を表示するために特別なメソッドを用意するのではなく to_s メソッドを定義しましょう。 必要な情報を文字列に変換するように記述すれば print は、to_s を自動的に呼びだします。 つまり print インスタンス と記述することで、to_s が呼びだされるのです。 サンプルとしては次のものがあります。

ここでは、盤の状態を示すのに使用しています。 盤クラスに to_s メソッドを用意することで、「盤」クラスのインスタンスを print するだけで盤の状態が表示されるのです。

require

単純な話だけれど。 テスト用のスクリプトを書くときなど、ついつい kconv.rb という名前を使ってしまったりします。 このとき、スクリプト中で require "kconv" としてしまうと、「あれ?」ということになります。 これは、カレントディレクトリの「kconv.rb」を require で読み込んでしまうためで、「あれれ?」ということになったりします。 つまり、kconv.rbrequire した kconv.rb で、さらに require しての繰り返し... ちょっとしたことですが... 使うファイル名は注意しましょうね。

Ruby の作者まつもとさんが連日 Ruby ホームページで「今日のひとこと」として Tips を紹介してます。 この中で上記の件の紹介がありました(98/9/18)。 スクリプトのファイル名を kconv.rb としていても require "kconv.o" と書けば OK だそうです。 このときダイナミックローディングのファイル名が .sl でも .so でも OK です。

そうだったんだ!


渡辺哲也(WATANABE Tetsuya): Tetsuya.WATANABE atmark nifty.com