UNIXプログラミング 「Ruby入門」 by 清川清

Ruby は手軽さと高機能さを併せ持つ、オブジェクト指向のスクリプト言語です。阪大の情報教育システムには、Ruby で作られたツールがたくさん用意されています。これらのツールの仕組みを理解し、改良していくためにも、Ruby の使い方をマスターしましょう。

なお、このページは講義の参照ページです。中身が手抜きなため、このページを読むだけではよくわからないかも知れませんがご了承ください。

目次:

Ruby とは


Ruby はスクリプト言語の手軽さで、本格的なオブジェクト指向プログラミングが可能な、オブジェクト指向スクリプト言語です。開発者は日本人、まつもと ゆきひろ 氏で、誕生日(初公開日)は1993年2月24日です。まだ比較的知名度は低いですが、着実にファンを増やしています。

まつもと氏によると、他のプログラミング言語に対する Ruby の特徴は以下のようになります。

Ruby のインストール


情報教育システムの Linux 環境には Ruby がすでにインストールされています。 自宅の Windows 環境で Ruby を試してみたくなったら、以下の手順でパッケージをインストールするとよいでしょう(2004年11月15日現在それぞれ最新)。

  1. Ruby 本体:ruby182-14RC9.exeを実行する。
  2. Ruby/GTK2:ruby-gtk2-0.10.0-2-i386-msvcrt-1.8.zipを展開して Ruby のインストールフォルダにコピーする。
  3. GLADE for Windows:gtk-win32-aio-2.4-rc22.exeを実行する。
  4. 環境変数 PATH に、ruby-install-dir\bin;gtk-install-dir\bin;gtk-install-dir\lib が含まれていることを確認して、なければ追加しておく。

これで準備完了(のはず)です。コマンドプロンプトを立ち上げて、以下がエラーなく実行できるか確かめましょう。

(例) (バージョン確認)
C:\>ruby -v
ruby 1.8.2 (2004-07-29) [i386-mswin32]
C:\>

(gtk2 が利用できることの確認)
C:\>ruby -e "require 'gtk2'"
C:\>

(gtk2 のサンプルプログラムの実行。窓が現れれば正解。)
C:\>ruby -e "require 'gtk2';Gtk.init;Gtk::Window.new.set_title('Hello Ruby-GNOME2 World!').show;Gtk.main"

より詳しいインストール方法がここにあります。うまくいかない人は参照してください。

Ruby の実行


Ruby プログラムの実行方法を紹介します。

コマンドラインから実行する

コマンドラインから、Ruby の引数に直接プログラムを与えます。ワンライナーともいいます。

$ ruby -e 'print "Hello world\n"'
Hello world
$ 

スクリプトファイルから実行する(1)

プログラムをスクリプトファイルに記述し、Ruby の引数として与えます。

$ cat hello.rb
print "Hello world\n"
$ ruby hello.rb
Hello world
$ 

スクリプトファイルから実行する(2)

スクリプトファイルに実行権限を与え、直接スクリプトを起動します。

$ cat hello2.rb
#!/usr/bin/ruby

print "Hello world\n"
$ chmod +x hello2.rb
$ ./hello2.rb
Hello world
$

Ruby の対話環境 irb を利用する

Ruby の対話環境 irb を利用して、対話的に Ruby を実行します。なお irb を終了するには exitquit と入力するか、Ctrl-D を入力します。

$ irb
irb(main):001:0> print "Hello world\n"
Hello world
=> nil
irb(main):002:0>

irb は、手軽にスクリプトの動作を確認できるだけでなく、以下のような特長があります。

irbのより詳しい解説はここを参照してください。

Ruby の特徴


Ruby の詳細に入る前に、簡単なプログラム例を通して Ruby の強力さに触れてみましょう。

正規表現の例(grep)

$ cat grep.rb
$pat = ARGV.shift
while gets
  print if /#{$pat}/
end
$ ruby grep.rb e grep.rb
while gets
end

〔補足〕

Note: Python と違って、インデントの深さは自由です。

〔以上補足〕

異なるデータ型を共通に扱う例

$ irb
irb(main):001:0> a = [123, "abc", [4, 5, 6]]
=> [123, "abc", [4, 5, 6]]
irb(main):002:0> a.each do |i|
irb(main):003:1*   puts i
irb(main):004:1> end
123
"abc"
4
5
6
=> [123, "abc", [4, 5, 6]]

標準データ型クラスの関数オーバーライドの例

$ irb
irb(main):001:0> 1 + 2 + 3 + 4 + 5
=> 15
irb(main):002:0> class Fixnum
irb(main):003:1>   def + (a)
irb(main):004:2>     self * a
irb(main):005:2>   end
irb(main):006:1> end
=> nil
irb(main):007:0> 1 + 2 + 3 + 4 + 5
=> 120

〔補足〕

Note: もちろん、こんなことをしてしまっては以降二度と足し算ができなくなります。

〔以上補足〕

ブロック渡しの例

$ irb
irb(main):001:0> def three_times
irb(main):002:1>   yield
irb(main):003:1>   yield
irb(main):004:1>   yield
irb(main):005:1> end
=> nil
irb(main):006:1> i = 1; three_times { i *= 2 }
=> 8
irb(main):007:1> three_times { print "abc\n" }
abc
abc
abc
=> nil

Ruby の基本文法


オブジェクト指向の原則

Ruby ではすべてがオブジェクトであり、オブジェクトが備えるメソッドを呼び出して処理を行います。

string = "ABCDE"
len = string.length
× len = length(string)

コメント

print "Hello world\n" # シャープ以降はコメント

=begin
↑この2行で挟まれた部分は
↓すべてコメント
=end

セパレータ

print "abc"; print "def" # ';' はセパレータ
print "123"              # 1行に1文の場合は ';' は不要

データ型

a = 123                               # 数値型
b = "abc"                             # 文字列型
c = [1, 2, 3]                         # 配列型    (c[0] == c[-3] == 1)
d = {100 => "perfect", "absent" => 0} # ハッシュ型(d[100] == "perfect")
e = 1..2                              # 範囲型

〔補足〕

Note: 変数に型はなく、データに型があります。変数はオブジェクトへのリファレンスです。

〔以上補足〕

変数

VAR  = "定数"                 # 大文字で始まる
$var = "グローバル変数"       # '$' で始まる

def func
  var = "ローカル変数"        # 小文字で始まる
end

class Class
  @@var = "クラス変数"        # '@@' で始まる
  def initialize
    @var = "インスタンス変数" # '@' で始まる
  end
end

演算子

a    = 100                                      # 代入
a, b = 10, 20                                   # 多重代入
a = (1 + 2) / (3 - 4) * 5                       # 四則演算
a = (1 && 2) and (3 || 4) or !(5)               # 論理演算
a = 7 % 2                                       # 剰余
a = 3 ** 0.5                                    # 累乗(この場合√3)
a = (1 == 2) or (3 != 4) or (5 < 6) or (7 <= 8) # 比較演算

〔補足〕

Note: && や || は and や or よりも優先順位が高いです。
Note: 演算子もやはり、実はすべてメソッドです。(先のオーバーライドの例参照)

〔以上補足〕

クラス


オブジェクト指向言語では、オブジェクトを単位として処理を行います。 クラスとは、あるオブジェクトに関する定義と挙動をまとめたものです。

$ cat animal.rb
class Animal
  def initialize
    @leg = 4
    @word = "ほげ"
  end

  def speak (n=1)
    n.times { print @word }
    print "\n"
  end
end

class Monkey < Animal       # 継承
  def initialize            # オーバーライド
    @leg  = 2
    @word = "うっきー"
  end
end

class Lion < Animal         # 継承
  def initialize            # オーバーライド
    @word = "がおー"
  end
end

animal = Animal.new         # インスタンス生成
monkey = Monkey.new
lion   = Lion.new

animal.speak(2)             # 多態性の例
monkey.speak
lion.speak(3)

$ ruby animal.rb
ほげほげ
うっきー
がおーがおーがおー

〔補足〕

Note: Ruby では多重継承はサポートしていませんが、代わりに module が用意されています。 module は class とほぼ同機能ですが、 (1) インスタンスが作れない(抽象クラスであることを保証)、 (2) サブクラスが作れない(継承関係がツリー構造になることを保証)、 (3) include 文で他のクラスに取り込まれ、そのクラスに自らの機能を提供する(Mix-in)、 という特徴があります。

〔以上補足〕

文字列


irb(main):001:0> p (("a" * 2) + "b") * 3 # 文字列の連結と繰り返し
"aabaabaab"
=> nil

irb(main):002:0> p "abcdefg"[3 .. 5]     # 部分文字列の参照
"def"
=> nil

irb(main):003:0> a = "abcdefg"
=> "abcdefg"
irb(main):004:0> a[3 .. 5] = "xyz"       # 部分文字列の置換
=> "xyz"
irb(main):005:0> a
=> "abcxyzg"

irb(main):006:0> p "abc\n"               # 制御文字を解釈
"abc\n"
=> nil
irb(main):007:0> p 'abc\n'               # 制御文字もそのまま
"abc\\n"
=> nil

irb(main):008:0> p 1.to_s + 2.3.to_s + [4, 5].to_s # 文字列への変換
12.345
=> nil

irb(main):009:0> p "12".to_i + "34".to_i # 整数への変換
46
=> nil

irb(main):010:0> p "a = #{a}"            # #{変数} は展開される
"a = abcxyzg"
=> nil

irb(main):011:0> p "abcdefg".length      # 文字列長
7
=> nil

irb(main):0123:0> p "abcde".reverse       # 反転する
"edcba"
=> nil

irb(main):013:0> p "abccabbcabbbccba".gsub(/ab+/, "xy") # 文字列の置換
"xyccxycxyccba"
=> nil

配列


irb(main):001:0> a = [1, 3, 4, 5, 7]     # 配列の生成
=> [1, 3, 4, 5, 7]

irb(main):002:0> b = [2, 3, 5, 7, 9]
=> [2, 3, 5, 7, 9]

irb(main):003:0> a.size                  # 大きさを返す
=> 5

irb(main):004:0> a | b                   # 和集合
=> [1, 3, 4, 5, 7, 2, 9]
irb(main):005:0> a & b                   # 積集合
=> [3, 5, 7]
irb(main):006:0> a - b                   # 差集合
=> [1, 4]

irb(main):007:0> a.shift                 # 先頭を取り出して削除
=> 1
irb(main):008:0> a
=> [3, 4, 5, 7]

irb(main):009:0> a.join("/")             # 指定文字で区切って連結
=> "3/4/5/7"

irb(main):010:0> a << 6                  # 要素の追加
=> [3, 4, 5, 7, 6]

irb(main):011:0> (a | b).sort            # ソート
=> [2, 3, 4, 5, 6, 7, 9]

irb(main):012:0> a.map { |i| i * i }     # 各要素を加工した配列を生成
=> [9, 16, 25, 49, 36]

正規表現


irb(main):001:0> reg = /osaka/              # 正規表現オブジェクトの生成。
=> /osaka/
irb(main):002:0> reg = Regexp.new("osaka")  # 別の生成方法。
=> /osaka/
irb(main):003:0> /osaka/ =~ "abc_osaka_xyz" # マッチする時は出現位置を返す
=> 4
irb(main):004:0> /osaka/ =~ "tokyo"         # マッチしない時は nil を返す
=> nil
irb(main):005:0> /^a(bc)*[d-f]g+h?i/        # 行頭がaで、bcが0個以上繰返し、
=> /^a(bc)*[d-f]g+h?/                       # d,e,fのどれかが続き、gが1個
                                            # 以上あり、hが0か1個ありiがある
irb(main):006:0> /^a(bc)*[d-f]g+h?i/ =~ "adgi"
=> 0
irb(main):006:0> /^a(bc)*[d-f]g+h?i/ =~ "abcbcfggghixyz"
=> 0
irb(main):006:0> /^a(bc)*[d-f]g+h?i/ =~ "acbcbfgggi"
=> nil

組込み変数


$_   : gets などで最後に読み込んだ文字列
$0   : 実行中の Ruby スクリプト名
$*   : Ruby スクリプトに与えられた引数
$&   : もっとも最近の正規表現にマッチした文字列
$.   : 最後に読んだ入力ファイルの行番号
ARGV : $* と同義
ARGF : 指定した引数を連結した仮想的な1つのファイルを示すオブジェクト

制御構文


条件分岐

if 文

a = 1

if a == 1
  puts "a は 1 です"
elsif a == 2
  puts "a は 2 です"
else
  puts "a はたくさんです"
end

puts "a は 1 です" if a == 1               # 後置型(修飾詞)

〔補足〕

Note: and を用いても if と同様の効果が出せます。
a == 1 and puts "a は 1 です"              # and による条件文

〔以上補足〕

unless 文

a = 2

unless a == 1
  puts "a は 1 ではないです"
else
  puts "a は 1 です"
end

puts "a は 1 ではないです" unless a == 1   # 後置型(修飾詞)

〔補足〕

Note: or を用いても unless と同様の効果が出せます。
a == 1 or puts "a は 1 ではないです"       # or による条件文

〔以上補足〕

case 文

case Time.now.hour              # 現在時刻を整数で取得
when 6..9                      # 範囲オブジェクトで条件分岐
  puts "おはよう"
when 9..17
  puts "こんにちは"
when 17..24
  puts "こんばんわ"
when 0..6
  puts "おやすみ"
else
  puts "そんな時計あるかい"   # どれにも該当しない場合
end

〔補足〕

Note: 範囲オブジェクトと数値の比較には === を用います。
if (18..35) === age
  puts "ストライクゾーン"
end

〔以上補足〕

繰り返し

while 文

$ cat factorial.rb
def factorial (i)
  a = i;
  while i > 1
    i -= 1
    a *= i
  end
  a                              # 返り値(while 文は nil を返す)
end

print factorial(gets.chop.to_i)  # 入力文字列.末尾の改行削除.数値化
$ ruby factorial.rb
10
3628800

〔補足〕

Note: Ruby ではメモリの許す限り大きな数を扱えます。
$ ruby factorial.rb
100
933262154439441526816992388562667004907159682643816214685929638952175999932
299156089414639761565182862536979208272237582511852109168640000000000000000
00000000

〔以上補足〕

until 文

def factorial (i)
  a = i;
  until i <= 1        # while a ⇔ until !a
    i -= 1
    a *= i
  end
  a
end

print factorial(gets.chop.to_i)

for 文

a = [1, 2, 3]
for i in a
  puts i
end

times メソッド

10.times do            # もちろん、変数にも使える
  puts "hello"
end

サンプル(数当てゲーム)

srand
a = rand(100)
while 1 do
  print "いくつでしょう?"
  i = gets.chop.to_i
  if a == i then
    puts "あたり!"
    break
  elsif a > i then
    puts "もっとおおきいよ"
  else
    puts "もっとちいさいよ"
  end
end

イテレータ


イテレータとは、ブロック付きのメソッド呼び出し(メソッドへのブロック渡し)を実現する機構で、Ruby の大きな特徴的機能です。

文字列のイテレータ

irb(main):001:0> a = "abc\ndef\nghi"
=> "abc\ndef\nghi"
irb(main):002:0> a.each do |i|      # do ... end は { ... } としてもよい
irb(main):003:1*   puts i
irb(main):004:1> end
abc
def
ghi
=> "abc\ndef\nghi"

配列のイテレータ

irb(main):001:0> a = [1, 10, 100]
=> [1, 10, 100]
irb(main):002:0> a.each do |i|
irb(main):003:1*   puts i
irb(main):004:1> end
1
10
100
=> [1, 10, 100]

ハッシュのイテレータ

irb(main):001:0> a = {"父" => 50, "母" => 48, "長男" => 20}
=> {"父" => 50, "母" => 48, "長男" => 20}
irb(main):002:0> a.keys.each do |i|        # 値のリストは a.values
irb(main):003:1*   puts i, ":", a[i], "\n"
irb(main):004:1> end
長男:20
母:48
父:50
=> ["長男", "母", "父"]

〔補足〕

Note: ハッシュはスタックなので定義した(push した)逆順に取り出されることに注意。

〔以上補足〕

ファイル


指定したファイルの表示

infile = open("file")     # 読み込みなので open("file", "r") でもよい
while line = infile.gets  # getc なら一文字ずつ
  print line
end
infile.close
infile = open("file")
infile.each do |line|     # 一行ずつ読む処理にイテレータを利用
  print line
end
infile.close
open("file") do |infile|  # open もブロックを受け付ける
  infile.each do |line|
    print line
  end
end                       # この場合 close は自動で行われる

指定したファイルの配列への読み込み

infile = open("file")
all = infile.readlines

指定したファイルをフィルタにかけた結果の読み込み

infile = open("| nkf -e file")
infile.each do |line|
  ...
end

サンプル(ファイルのマルチコピー)

if ARGV.size >= 2
  infile  = ARGV.shift
  inf = open(infile, "r")

  outf = []
  ARGV.each do |i|
    outf << open(i, "w")
  end

  while (line = inf.gets)
    outf.each do |i|
      i.write(line)
    end
  end
end

〔補足〕

Note: 既存ファイルへの上書きのチェックをしていないので、動作確認の際は注意してください。

〔以上補足〕

ソケット


※工事中です

スレッド


※工事中です

Ruby/GTK2


最小の Ruby/GTK2 プログラム

require 'gtk2'            # 必須
Gtk.init                  # 必須(初期化)

window = Gtk::Window.new  # ウィンドウを作って
window.show               # 表示する

Gtk.main                  # 必須(Gtk のイベント待ちループに入る)
Note: Note:
Vine Linux での表示 Windows での表示

〔補足〕

Note: このプログラムはシェルから強制終了させるしかありません。

〔以上補足〕

Ruby/GTK2 で Hello world

require 'gtk2'
Gtk.init

button = Gtk::Button.new("Hello world")  # ボタンを生成
button.signal_connect("clicked") {       # クリック時の処理
  puts "Hello world"
}

window = Gtk::Window.new                 # ウィンドウを生成
window.add(button)                       # ウィンドウにボタンを登録
window.signal_connect("delete_event") {  # delete イベント発生時の処理
  puts "delete event occurred"
  Gtk.main_quit                          # Gtk のループを終了
}
window.show_all                          # 全オブジェクトを表示する

Gtk.main
Note: Note:
Vine Linux での表示 Windows での表示
$ ruby hello_world.rb
Hello world
Hello world
Hello world
delete event occurred

$

コールバックの利用

require 'gtk2'
Gtk.init

def clicked(widget)                      # クリック時に呼ぶ関数
  puts "I am #{widget.label}."           # 引数のラベル名を表示
end

window = Gtk::Window.new
window.border_width = 10                 # ボタンの周囲に隙間を空ける
window.signal_connect('delete_event') {
  Gtk.main_quit
}

box = Gtk::HBox.new(false, 0)            # ボタンを横に並べる箱を作る
window.add(box)

button1 = Gtk::Button.new("Button 1")    # 1つ目のボタンの定義
button1.signal_connect("clicked") { |w|  # ウィジェットを受取る
  clicked(w)                             # 受けたウィジェットを引数に
}
box.pack_start(button1, true, true, 0)   # 箱にボタン1を収納

button2 = Gtk::Button.new("Button 2")    # 2つ目のボタンを定義
button2.signal_connect("clicked") { |w|  # シグナルハンドラも同様に定義
  clicked(w)
}
box.pack_start(button2, true, true, 0)   # 箱にボタン2を収納

window.show_all
Gtk.main
Note: Note:
Vine Linux での表示 Windows での表示
$ ruby callback.rb
I am Button 1.
I am Button 1.
I am Button 2.
I am Button 1.
I am Button 2.

$

テキストエントリ

require 'gtk2'
Gtk.init

entry = Gtk::Entry.new
entry.set_visibility(true)             # false にしてみましょう

button = Gtk::Button.new("click!")
button.signal_connect("clicked") do
  p entry.text
end

vbox = Gtk::VBox.new
vbox.pack_start(entry)
vbox.pack_start(button)

window = Gtk::Window.new
window.signal_connect('delete_event') {
  Gtk.main_quit
}

window.add(vbox)
window.show_all

Gtk.main
Note: Note:
Vine Linux での表示 Windows での表示

開発環境・デバッグ


リファレンスマニュアル

プロファイラ: profile.rb

require "profile" とするか ruby のオプションで -r profile とする。 メソッド毎に呼出し回数や処理時間を表示するが、動作が遅くなるという問題もある。 詳しくはこちら

デバッガ: debug.rb

require "debug" とするか ruby のオプションで -r debug とする。 詳しくはこちら

RDE (Ruby Development Environment)

Emacs 関係

(autoload 'ruby-mode "ruby-mode"
  "Mode for editing ruby source files")
(setq auto-mode-alist
      (append '(("\\.rb$" . ruby-mode)) auto-mode-alist))
(setq interpreter-mode-alist (append '(("ruby" .ruby-mode))
                                     interpreter-mode-alist))
(autoload 'run-ruby "inf-ruby"
  "Run an inferior Ruby process")
(autoload 'inf-ruby-keys "inf-ruby"
  "set local key defs for inf-ruby in ruby-mode")
(add-hook 'ruby-mode-hook
          '(lambda ()
            (inf-ruby-keys)
          ))

課題


以下のそれぞれの課題のプログラムを作成し、メールで teachers@ime.cmc. ... まで送ること。 (期限: 11月10日午後6時)

その1

指定した英文テキストファイルを読み込み、各単語の先頭を大文字にする Ruby スクリプト upcase.rb を作りなさい。 (ヒント: String#split, String#upcase)

$ cat test.txt
my name is osaka taro.
$ ruby upcase.rb test.txt
My Name Is Osaka Taro.

その2

はじめ "0" が表示され、クリックするたびに "1", "2", ... とラベルがインクリメントされるボタンと、 クリックするとプログラムを終了する "Quit" というラベルのボタンが縦に並んだウィンドウを表示するプログラム count.rb を作りなさい。

その3

テキストエントリから入力したファイルの行数と総バイト数をボタンのラベルに表示する Ruby/GTK2 プログラム filesize.rb を作りなさい。

参考情報


ポータル

入門1(気楽に読めるもの)

入門2(やや詳しい解説)

インタビュー・読み物

GTK2関連

リンク集


Last update: 2005-10-21