Ruby

2007/10/16更新

対応バージョン: 1.8.6

RubyでXMLデータをパースするにはいくつかの方法があるが、ここではRuby 1.8に標準添付されているREXMLライブラリを使用してRSS2.0のファイルをパースする(RSSライブラリは使用しない)。

尚、RSS/Atomのバージョン毎の差異吸収やエラー処理等は省いている。

パース対象RSS (2.0)

<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0">
  <channel>
    <title>sample blog</title>
    <link>http://foo.bar.com/</link>
    <description>サンプルブログ</description>
    <language>ja</language>

    <item>
      <title>使い方難しい。</title>
      <link>http://foo.bar.com/article/2</link>
      <pubDate>Thu, 17 Oct 2007 17:13:00 +0900</pubDate>
    </item>

    <item>
      <title>ブログ始めました。</title>
      <link>http://foo.bar.com/article/1</link>
      <pubDate>Wed, 13 Oct 2007 12:08:00 +0900</pubDate>
    </item>

  </channel>
</rss>

ソース

require "rexml/document"
include REXML

require "time"

doc = Document.new File.new("rss20.xml")

puts "<ul>"

doc.elements.each("rss/channel/item") {|item|
  title = item.elements["title"].text
  link  = item.elements["link"].text
  pubDate = item.elements["pubDate"].text
  date  = Time.rfc2822(pubDate).strftime("%m/%d")

  puts "  <li><a href=\"#{link}\">#{title}</a> (#{date})</li>"
}

puts "</ul>"

実行結果

<ul>
  <li><a href="http://foo.bar.com/article/2">使い方難しい。</a> (10/17)</li>
  <li><a href="http://foo.bar.com/article/1">ブログ始めました。</a> (10/13)</li>
</ul>

実行環境

Fedora 7 (2.6.22.9-91.fc7)

2007/8/21更新

対応バージョン: 1.8

スレッドの生成方法

スレッドは以下の方法で簡単に生成できる。

require "thread" 

Thread.start {          # 新しいスレッドを開始し、その上で処理実行
  while true
    puts "thread 1"
    sleep(1)
  end
}

while true		# 既存スレッド上での処理
  puts "thread 2"
  sleep(1)
end

この処理を実行すると各々のスレッドが独立して動作するので結果は以下のようになる。

thread 1 ← 新規スレッド上の処理開始
thread 2 ← 既存スレッド上の処理開始
thread 1
thread 2
thread 1
thread 2
thread 1
thread 2
:

2007/7/31更新

対応バージョン: 1.8

文字列の変更を行う

reverse

文字列並び順入れ替え(リバース)

upcase

大文字変換

downcase

子文字変換

swapcase

大文字/小文字入替

capitalize

先頭のみ大文字変換(残りは小文字変換)

例)

data = "aBcDe"

puts data.reverse     # リバース
=> "eDcBa"

puts data.upcase      # 大文字
=> "ABCDE"

puts data.downcase    # 小文字
=> "abcde"

puts data.swapcase    # 大文字/小文字入替
=> "AbCdE"

puts data.capitalize  # 先頭大文字化
=> "Abcde"

文字列の一部を取り出す

取り出したい位置と取り出す文字数を指定する。

data = "string"

puts data[0, 3]      # 先頭から3バイト
=> "str"

puts data[2, 100]    # 文字列長を越えて指定しても問題ない
=> "ring"

puts data[-1, 1]     # 最後から1バイト
=> "g"

文字列の桁揃えを行う

以下のメソッドを使用する。

ljust : 左詰め
center : センタリング
rjust : 右詰め

例)

data = "abc"

puts '|' + data.ljust(10)  + '|'   # 左詰め
=> "|abc       |"

puts '|' + data.center(10) + '|'   # センタリング
=> "|   abc    |"

puts '|' + data.rjust(10)  + '|'   # 右詰め
=> "|       abc|"

文字列中の空白文字を取り除く

先頭と末尾の空白文字を全て取り除くにはstripを、先頭のみの場合はlstripを、末尾のみの場合はrstripをそれぞれ使用する。

空白文字の定義は"\s\t\r\n\f\v"。

例) strip

p "  a b   c    ".strip

=> "a b  c"

例) lstrip

p "  a b   c    ".lstrip

=> "a b  c    "

例) rstrip

p "  a b   c    ".rstrip

=> "  a b  c"

2007/6/21更新

対応バージョン: 1.8

マッチングパターン

行頭「^」

文字列の先頭、あるいは改行文字の直後の位置にマッチする。

例)

p "aaa".gsub(/^a/, "1")
=> "1aa"

p "aaa\naaa".gsub(/^a/, "1")
=> "1aa\n1aa"

行末「$」

文字列の末尾、あるいは改行文字の直前の位置にマッチする。

例)

p "aaa".gsub(/a$/, "1")
=> "aa1"

p "aaa\naaa".gsub(/a$/, "1")
=> "aa1\naa1"

英数字「\w」(= [0-9A-Za-z_])

非英数字「\W」(\w以外)

空白文字「\s」(= [\t\n\r\f])

非空白文字「\S」(\s以外)

数字「\d」(= [0-9])

非数字「\D」(\d以外)

ワード境界「\b」

英単語で「limit」はマッチさせたいが「unlimited」はマッチさせたくない場合などにこのワード境界を使用する。

例)

p "limit".gsub(/\blimit\b/, "match")
=> "match"

p "unlimited".gsub(/\blimit\b/, "match")
=> "unlimited" ← マッチしていない

任意の1文字「.」

数字やアルファベット、記号など、文字種は問わず、任意の1文字にマッチする。

例) 「1.3 (1で始まって3で終わる3文字の文字列)」が一致する文字列

113
193
1a3 など

0回以上の繰り返し「*」

いわゆるワイルドカードと同じ文字を使用するが、正規表現においては扱いが微妙に違う。

ワイルドカードでは「任意の複数文字に一致」を意味するが、正規表現では「直前の文字を0回以上繰り返したものに一致」を意味する。

例) 「ab*c」が一致する文字列

ac
abc
abbc
abbbc など

直前の文字の0~1回の繰り返し「?」

例) 「ab?c」に一致する文字列

ac
abc など

直前の文字の1回以上の繰り返し「+」

例) 「ab+c」に一致する文字列

abc
abbc
abbbc など

「ab?c」と違い、acには一致しない

いずれかに一致「[]」

ある規則性を持った文字列のみを一致させたい場合、「[」と「]」を使用する。

この2つで複数の文字を挟むと「中に並べられた文字のどれかに一致」という意味になる。

例えば「母音だけが並んだ文字列」を指定した場合は以下のようにすればよい。

[aiueo][aiueo]*

ここで同じものを2つ並べているのは前述のように「*」が「0回以上の繰り返し」を意味するからである。これは「+」を使用して以下の方法で指定できる。

[aiueo]+

もし並べる文字に規則性があるなら省略形も使える。

始まりの文字と終わりの文字をハイフンでつなげて「[a-z]」とすれば英小文字のいずれか、「[0-9]」とすれば数字のいずれかに一致する。

例) 英単語に一致

[a-zA-Z]+

いずれかの文字以外(否定)「[^」

「いずれかの文字」ではなく、「いずれかの文字以外」という指定もできる。この場合は「[」の直後に「^」を置く。

以下のようにすると「母音以外の1文字」という条件になる。

p "abc".gsub(/[^aiueo]/, "_")
=> "a__"

複数パターンのいずれか「|」

文字ではなく複数のパターンのいずれか、という場合は「|」を使用する。

p "abc".gsub(/abc|def/, "_")
=> "_"

とすれば、「abc」か「def」のどちらかに一致する。

パターンの繰り返し

時として「一致する回数を指定する」という表現をしたくなる。

例えば、ある文字列(プログラム中の変数名など)が9文字以上かどうかを調べたいような場合、通常なら

[a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-]
[a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-]
[a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-]+

と記述するだろうが、このような方法ではタイプミスの可能性もあるし、もっと文字数が増えた場合に作業が困難になるので、以下のようにして回数を指定できる。

[a-zA-z0-9_-]{9,}

「{n}」で直前のパターンをn回繰り返し、「{n,}」ではn回以上、「{n,m}」ではn回以上m回以下を意味する。

グループにまとめる

正規表現をグループ化するには、「(」と「)」でくくる。

例えば「abcを1回以上繰り返した後にdefを1回以上繰り返したもの」という条件は以下のように記述する。

(abc)+(def)+

パターンマッチしたデータの取得

パターンにマッチしたデータはそれぞれ以下の組込変数で取得できる。

$& : マッチデータ
$` : マッチデータ「前」文字列
$' : マッチデータ「後」文字列
data = "123abc789"

data.match(/abc/)

target     = $&
pre_match  = $`
post_match = $'

p "#{target} : #{pre_match} : #{post_match}"

=> "abc : 123 : 789"

また、括弧で条件を指定した場合は以下のような方法でマッチデータを取得できる。

$1 : 1番目の括弧にマッチしたデータ
$2 : 2番目の括弧にマッチしたデータ
$3 : 3番目の括弧にマッチしたデータ

:

$n : n番目の括弧にマッチしたデータ
$+ : 最後の括弧にマッチしたデータ
data = "123abc789"

data.match(/(123)(abc)(789)/)

match1 = $1
match2 = $2
match3 = $3

match_end = $+

p "#{match1} : #{match2} : #{match3} : #{match_end}"

=> "123 : abc : 789 : 789"

応用

case文で正規表現を使う

以下のように通常の正規表現で記述できる。また通常の条件文も混在できる。

case <比較データ>

when /<正規表現>/
  処理

when /<正規表現>/
  処理

when "<通常の条件文>"
  処理

else
  処理

end

例)

case string

when /^type/
  先頭が「type」で始まっている場合の処理

when /^$/
  空行の処理

when "general"
  内容が"general"と一致した時の処理

else
  それ以外の処理

end

これは以下のif文と同義である。

if string =~ /^type/
  先頭が「type」で始まっている場合の処理

elsif string =~ /^$/
  空行の処理

elsif string == "general"
  内容が"general"と一致した時の処理

else
  それ以外の処理

end

URLからホスト/ポート/パスの各要素を取り出す

以下のようにURLを厳密に表現したマッチパターンを使って各要素に分解できる。

url = "http://foo.bar.com:8080/contents"

if url =~ %r!http://(.*?)(?::(\d+))?(/.*)!
  host = $1 if $1 != nil        # ホスト
  port = $2 if $2 != nil        # ポート番号
  path = $3 if $3 != nil        # パス
end

puts host,port,path

=> foo.bar.com
=> 8080
=> /contents

URLからタグを取り除く

「<.*?>」という正規表現を使用すれば簡単にタグが取り除ける。

例)

p '<a href="http://foo.bar.com/">title</a>'.gsub(/<.*?>/, "")

=> "title"

ただしこの方法だと「<データ>」のように通常の文章として書かれている文字列もタグとして取り除かれてしまうので、そのような文字列が文書内に存在する場合は削除対象とすべきパターンを厳密に指定する必要がある。

2007/11/7更新

対応バージョン: 1.8

簡単なTCPサーバを作成する

TCPServerクラスを使用する。

例) daytimeサーバのように現在日時を返す

require 'socket'

s_port = 8888                         # 待ち受けポート

s = TCPServer.new(s_port)             # サーバ側ソケット生成

loop {                                # 処理ループ
  c = s.accept                        # 接続受付

  Thread.start {                      # 入出力(スレッド内)
    c.print Time.now.strftime("%c")   # 現在日時出力 
    c.close                           # 接続終了
  }
}

簡単なHTTPクライアントを作成する

HTTPクライアントを作成するには'net/http'を使うと便利だが、'socket'を使うことで、より汎用的なTCP/IPクライアントが作成できる。

require 'socket'

s = TCPSocket.new("foo.bar.com", 80)  # ソケットを開く

s.print "GET / HTTP/1.0\r\n\r\n"      # ソケットにGETリクエスト送信

print s.read                          # ソケットからメッセージを読み込み出力

s.close                               # ソケットを閉じる

Proxy経由でHTTPリクエストを送信する

Net::HTTP::Proxyを使用して以下のようにする。

require 'net/http'

to_host = "foo.bar.com"
req = "/index.html"

Net::HTTP::Proxy(proxy_host,proxy_port).start(to_host) {|http|
  response = http.get(req)
  print response.body
}

proxy_hostがnilの場合はNet::HTTPそのものを返すのでProxy環境以外でも正常に動作する。

例えばproxy_host,proxy_portを環境変数HTTP_PROXY等から取得するようにしておけば、Proxy有/無のどちらの環境でも同一ソースで動作する。

トラックバックを送信する

トラックバック送信は送信元データに関する以下の情報をHTTPのPOSTメソッドに載せて送信先URLに送ることで可能となる。

title

記事タイトル

excerpt

記事要約

url

記事URL

blog_name

ブログタイトル

以下にRubyによりトラックバック送信機能の実装例を示す。

require 'net/http'
require 'cgi'

#
# パラメータ設定
#
title   = "XXXについて"                 # 記事タイトル
excerpt = "XXXは本来こういうもので..."  # 記事要約
url     = "http://www.from.net/item/12" # 記事URL
name    = "Myブログ"                    # ブログタイトル

to_host = "www.to.net"                  # 送信先ホスト
to_path = "/recv.cgi/45"                # 送信先パス

#
# トラックバック送信
#
req = "title=#{title}&" \               # 送信パラメータ生成
    + "excerpt=#{excerpt}&" \
    + "url=#{url}&" \
    + "blog_name=#{name}"

req = CGI.escape(req)                   # URLエンコード

Net::HTTP.start(to_host) {|http|        # トラックバック送信
  response = http.post(to_path, req)
}

英文メールを送信する

英文メールを送信するには'net/smtp'を使うと便利である。

require 'net/smtp'
require 'time'

#
# 各種設定
#
smtp_server = "<SMTPサーバ>"
helo_domain = "<HELOドメイン>"
from_addr   = "<Fromアドレス>"
to_addr     = "<Toアドレス>"
subject     = "<件名>"
date        = Time.now.rfc2822
msg_id      = "<メッセージID>"
msg         = "<本文>"

#
# メールデータ作成
#
mail_data = <<EOD
From: #{from_addr}
To: #{to_addr}
Subject: #{subject}
Date: #{date}
Message-Id: #{msg_id}

#{msg}
EOD

#
# メール送信
#
Net::SMTP.start(smtp_server, 25, helo_domain) {|smtp|
  smtp.send_mail mail_data, from_addr, to_addr
}

2012/11/11更新

対応バージョン: 1.9

Ruby-1.9になってM17Nまわりの仕様が変わり、スクリプト自体の文字エンコーディングをマジックコメントで明示するようになった。

UTF-8の場合
#!/usr/local/bin/ruby
#coding:utf-8

ただマジックコメントはファイル単位で機能するため、スクリプト内から外部のファイルを読み込んで文字列処理などを行う場合はマジックコメントは機能せず、そのプログラムを実行する環境のLANG環境変数で指定されたエンコーディングに従って動作する。

結果、コマンドラインから実行するとLANG環境変数がUTF-8に設定されていてうまく動く場合でも、cronやCGIなど起動元が別機構の場合はそれぞれの機構の作法に従って環境変数を設定しておかないとエラーになってしまう。

例えばカンマ区切りのデータをパイプ(|)区切りに変更しようとした場合、LANG環境変数がデフォルトのCのままだと以下のようなエラーになる。

スクリプト
#!/usr/local/bin/ruby
#coding:utf-8

# (途中の処理は省略)

data = line.split(",").join("|")
エラー
in `split': invalid byte sequence in US-invalid byte sequence in US-ASCII
(ArgumentError)

以下にcronとApacheのLANG設定方法を示す。他の機構の場合はそれぞれの作法に従って環境変数を設定する。

cron

方法1)

先頭で環境変数を設定するとそのcrontab内の実行プログラム全てで有効になる

LANG=ja_JP.UTF-8

# 毎日3:00にバックアップを実行する
0 3 * * * backup.rb

# 5分おきにシステム状態をチェックする
*/5 * * * * syscheck.rb
方法2)

各プログラムの先頭で環境変数を設定するとプログラムによって環境変数を変えることができる

# 毎日3:00にバックアップを実行する
0 3 * * * env LANG=C backup.rb

# 5分おきにシステム状態をチェックする
*/5 * * * * env LANG=ja_JP.UTF-8 syscheck.rb

Apache

httpd.confにてSetEnvディレクティブを設定する。

SetEnv LANG ja_JP.UTF-8

関連資料・記事

2007/8/1更新

対応バージョン: 1.8

ハッシュをソートする

ハッシュのキー、値それぞれでソート可能である。

例)

data = {}

data[1] = 1
data[0] = 5
data[2] = 4
data[4] = 3
data[3] = 2

p data.sort {|x, y| x[0] <=> y[0]}	# キーでソート
=> [[0, 5], [1, 1], [2, 4], [3, 2], [4, 3]]

p data.sort {|x, y| x[1] <=> y[1]}	# 値でソート
=> [[1, 1], [3, 2], [4, 3], [2, 4], [0, 5]]

(*) xとyの[0]、[1]にはそれぞれ「キー」、「値」が入る。

逆順にソートしたい場合は以下のようにいったんソートしたものを逆順に並び替えることで実現可能である(reverseは逆順ソートではなく、単に逆に並べ替えるだけである)。

p data.sort.reverse {|x, y| x[0] <=> y[0]}
=> [[4, 3], [3, 2], [2, 4], [1, 1], [0, 5]]

参考までに、逆順ソートした値を使用する場合は以下のようにする(暗黙的にキー順にソートされる)。

data.sort.reverse_each {|key, item|
  puts "#{key} #{item}"
}

=> 4 3
=> 3 2
=> 2 4
=> 1 1
=> 0 5

ハッシュの添字に変数を使用する

最初に{}でハッシュを初期化し、ハッシュのキー部に変数名を「"」でくくって指定する。

例)

data = {}			# ハッシュ初期化

key = "key_a"			# キー設定

data["#{key}"] = "abc"		# ハッシュ設定

p data["#{key}"]

=> "abc"

2007/8/21更新

対応バージョン: 1.8

ファイルの属性を得る

以下のメソッドを使用する。

dirname : パス名
basename : ファイル名

例)

file = "/etc/shells"

puts File.dirname(file)
puts File.basename(file)

=> /etc
=> shells
ctime : 作成時刻
mtime : 最終更新時刻
atime : 最終アクセス時刻

例)

file = "/etc/shells"

puts File.ctime(file).to_s
puts File.mtime(file).to_s
puts File.atime(file).to_s

=> Wed Apr 04 21:08:34 +0900 2007
=> Wed Apr 04 21:08:34 +0900 2007
=> Tue Aug 21 16:54:40 +0900 2007

ファイルの種類を調べる

File.ftypeを使用するか、FileTestを使用する。

ここではFile.ftypeの場合を示す。

case File.ftype(target)

when "file"
  puts "通常のファイル"

when "directory"
  puts "ディレクトリ"

when "link"
  puts "シンボリックリンク"

when "characterSpecial"
  puts "キャラクタスペシャルファイル"

when "blockSpecial"
  puts "ブロックスペシャルファイル"

when "fifo"
  puts "名前付きパイプ(FIFO)"

when "socket"
  puts "ソケット"

when "unknown"
  puts "不明"

end

ヒアドキュメントの内容をファイルに出力する

ファイルオブジェクトのメソッドとしてprintを使用する。

例)

f = File.open("foo", "w")

f.print <<END
line1
line2
:
END

f.close

2007/12/4更新

対応バージョン: 1.8

環境変数の参照/更新

環境変数の参照はENV['<環境変数名>']で可能である。

また、環境変数への値の設定はENV['<環境変数名>'] = 「値」で可能である。

例)

p ENV['SHELL']

=> "/bin/bash"

ライブラリサーチパスの参照/追加

既存のライブラリサーチパスは$LOAD_PATHに格納されているので、これを参照したりパスを追加したりできる。

パス参照

puts $LOAD_PATH

=> /usr/lib/ruby/site_ruby/1.8
=> /usr/lib/ruby/site_ruby/1.8/i386-linux
=> /usr/lib/ruby/site_ruby
=> /usr/lib/site_ruby/1.8
=> /usr/lib/site_ruby/1.8/i386-linux
=> /usr/lib/site_ruby
=> /usr/lib/ruby/1.8
=> /usr/lib/ruby/1.8/i386-linux
=> .

パス追加

$LOAD_PATH.push("/foo/bar/lib")

require "mylib"

Rubyスクリプトに環境変数の値を引き渡す

外部で設定した環境変数の値をRubyスクリプトに引き渡したい場合、以下のようにしてスクリプト自体は/bin/shで作成し、途中からRubyスクリプトを記述してそれを/bin/shから呼び出すという方法がある。

例) LD_LIBRARY_PATH環境変数の値をRubyスクリプト内で利用した場合

#!/bin/sh

LD_LIBRARY_PATH=/Users/foo/lib
export LD_LIBRARY_PATH

exec ruby -S -x "$0" "$@"

#!ruby
# ------- ここからRubyスクリプト -------

:
<Rubyのスクリプト>
:

2007/10/17更新

対応バージョン: 1.8

日時データの要素を取り出す

以下の各メソッドを使用する。

hour
min
sec

時、分、秒を整数で返す

year
month(mon)
day(mday)

年、月、日を整数で返す

wday

曜日を0〜6の整数で返す

0:日、1:月、2:火、3:水、4:木、5:金、6:土

yday

1月1日を1とした通算日(1〜366)を整数で返す

isdst(dst?)

夏時間があるならtrueを、なければfalseを返す

zone

タイムゾーンを表す文字列を返す(例:JST)

(*) C言語のtm構造体とは異なり、monthは1月に対して1を返し、yearは1998年に対して1998を返す。またydayは1から数える。

特定のフォーマットの日時データをTimeオブジェクトに変換する

以下のフォーマットをサポートする。

rfc2822
rfc822

RFC 2822で定義されているdate-timeフォーマット。この形式はRFC 822で定義されてRFC 1123で更新された形式と同じである。

このフォーマットはRSS2.0で使われる。

httpdate

RFC 2616で定義されているHTTP-dateのrfc1123-dateフォーマット。

このフォーマットはHTTPレスポンスヘッダで使われる。

xmlschema
iso8601

XML Schemaで定義されているdateTimeフォーマット。

このフォーマットはRSS1.0で使われる。

例)

date = Time.rfc2822("Wed, 22 Aug 2007 01:21:38 +0900")
date = Time.rfc822("Wed, 22 Aug 2007 01:21:38 +0900")

date = Time.httpdate("Wed, 22 Aug 2007 01:21:38 GMT")

date = Time.xmlschema("2007-02-09T23:41:19+09:00")
date = Time.iso8601("2007-02-09T23:41:19+09:00")

これらTimeオブジェクトの内容をテキストに変換するにはstrftimeを使用する。

例)

p date.strftime("%Y/%m/%d %H:%M")

日時データをテキストに変換する

strftimeメソッドを使用する。

フォーマット文字列は以下のものが指定できる。

日付/時刻複合

%c

日付と時刻

例)

now = Time.now
p now.strftime("%c")
=> "Tue Oct 16 20:30:07 2007"

日付

%x

年月日(例:08/22/07)

%Y

西暦4桁

%y

西暦の下2桁(00-99)

%m

月(01-12)

%B

月の名称(January, February ... )

%b

月の省略名(Jan, Feb ... )

%d

日(01-31)

%w

曜日を表す数。日曜日が0(0-6)

%A

曜日の名称(Sunday, Monday ... )

%a

曜日の省略名(Sun, Mon ... )

%U

週を表す数。最初の「日曜日」が第1週の始まり(00-53)

%W

週を表す数。最初の「月曜日」が第1週の始まり(00-53)

%j

年中の通算日(001-366)

時刻

%H

24時間制の時(00-23)

%I

12時間制の時(01-12)

%M

分(00-59)

%S

秒(00-60) (60はうるう秒)

%p

午前または午後(AM,PM)

%X

時刻(HH:MM:SS)

%Z

タイムゾーン(例:JST)

様々なフォーマットの日時データを加工する

ParseDate.parsedateを使用する。

第1パラメータに加工したい日時データ(テキスト)を指定すると、与えられた日時表現を解析して見いだした要素を配列(年,月,日,時,分,秒,タイムゾーン,曜日)で返す。

解析できなかった要素にはnilがセットされる。

また、第2パラメータがtrueの場合でかつ年が0〜99の範囲の場合、年の下2桁の表現であるとみなし以下のルールに従って年の上2桁を補完する。

年 >= 69

1900年代とみなす(年 + 1900)

年 < 69

2000年代とみなす(年 + 2000)

他にも様々なフォーマットの日時表現を受け付けるので以下に例を示す。

require 'parsedate'

date_list = [
  "Sat Aug 28 21:45:09 1999",
  "Saturday, 28-Aug-99 21:45:09 GMT",
  "99-08-28",
  "01-08-28",
  "Sat",
  "Saturday",
  "1999-08-28",
  "21:45:09",
  "09:45:09 PM",
  "1999-08-28T21:45:09+0900",
  "19990828 214509",
  "H11.08.28T21:45:09Z",
  "Sat Aug 28 21:45:09 1999",
  "Sat Aug 28 21:45:09 JST 1999",
  "Sat, 28 Aug 1999 21:45:09 -0400",
  "Saturday, 28-Aug-99 21:45:09 GMT",
  "08/28/1999",
  "1999/08/28",
]

date_list.each {|date|
  print "#{date} => "
  p ParseDate.parsedate(date, true)
}

実行結果

Sat Aug 28 21:45:09 1999 => [1999, 8, 28, 21, 45, 9, nil, 6]
Saturday, 28-Aug-99 21:45:09 GMT => [1999, 8, 28, 21, 45, 9, "GMT", 6]
99-08-28 => [1999, 8, 28, nil, nil, nil, nil, nil]
01-08-28 => [2001, 8, 28, nil, nil, nil, nil, nil]
Sat => [nil, nil, nil, nil, nil, nil, nil, 6]
Saturday => [nil, nil, nil, nil, nil, nil, nil, 6]
1999-08-28 => [1999, 8, 28, nil, nil, nil, nil, nil]
21:45:09 => [nil, nil, nil, 21, 45, 9, nil, nil]
09:45:09 PM => [nil, nil, nil, 21, 45, 9, nil, nil]
1999-08-28T21:45:09+0900 => [1999, 8, 28, 21, 45, 9, "+0900", nil]
19990828 214509 => [1999, 8, 28, 21, 45, 9, nil, nil]
H11.08.28T21:45:09Z => [1999, 8, 28, 21, 45, 9, "Z", nil]
Sat Aug 28 21:45:09 1999 => [1999, 8, 28, 21, 45, 9, nil, 6]
Sat Aug 28 21:45:09 JST 1999 => [1999, 8, 28, 21, 45, 9, "JST", 6]
Sat, 28 Aug 1999 21:45:09 -0400 => [1999, 8, 28, 21, 45, 9, "-0400", 6]
Saturday, 28-Aug-99 21:45:09 GMT => [1999, 8, 28, 21, 45, 9, "GMT", 6]
08/28/1999 => [1999, 8, 28, nil, nil, nil, nil, nil]
1999/08/28 => [1999, 8, 28, nil, nil, nil, nil, nil]

2007/8/19更新

対応バージョン: 1.8

ASCII文字 - コード値相互変換

ASCII文字をコード値に変換するには文字列のうち1文字だけを参照すればよい。

s = "AB"

p s[0]
=> 65

p s[1]
=> 66

逆にコード値をASCII文字に変換するには以下のような方法がある。

組込み関数のsprintf、format
Integer#chrメソッド
String#%メソッド
p sprintf("%c", 65)
=> "A"

p format("%c", 66)
=> "B"

p 67.chr
=> "C"

p "%c" % 68
=> "D"

これを応用すれば、例えばExcelのセル記号(A,B,C...)を10進から変換することも簡単にできる。

def num2excel(num)
  high = num / 26               # セル番号の上位を取り出す
  row  = num % 26               # セル番号の下位を取り出す  

  if high > 0                   # セル番号が27以上の場合
    high_char = (high + 64).chr # セル番号上位を文字列に(A〜Z)
  else                          # セル番号が26以下の場合
    high_char = ""              # セル番号上位は不要
  end

  row_char = (row + 65).chr     # セル番号下位を文字列に(A〜Z)

  out = sprintf("%s%s", high_char, row_char)
                                # セル番号上位+下位を結合
  return out
end

256.times {|i|                  # Excel2003の最大セル数
  char = num2excel(i)
  puts sprintf("%03d = %s\n", i + 1, char)
}

(結果)

001 = A
002 = B
:
025 = Y
026 = Z
027 = AA
028 = AB
:
255 = IX
256 = IV

文字列をURLエンコードする

CGI.escapeを使用する。

require "cgi"

p CGI.escape("@##")

=> "%40%23%23"

11

文字列を実体参照化する

2007/8/19

1.8

HTMLやXMLの中でエスケープすべき文字「<」「>」「&」「"」については以下のように個別に実体参照化してもよいが、

line = "<エスケープすべき文字列>"

line.gsub!(/</, "&lt;");
line.gsub!(/>/, "&gt;");
line.gsub!(/&/, "&amp;");
line.gsub!(/"/, "&quot;");

CGI.escapeHTMLを使用すればもっと簡単に処理できる。

require "cgi"

p CGI.escapeHTML("<s>try & error</s>")

=> "&lt;s&gt;try &amp; error&lt;/s&gt;"

2007/8/21更新

対応バージョン: 1.8

例外処理の記述方法

以下の節を組み合わせて使用する。

begin : 主処理
rescure : 例外処理(オプション)
ensure : 終了前処理(オプション)

例)

file = "./non_exist"

begin   # 主処理

  f = open(file)

  puts "opened => #{file}"

rescue  # begin節内の例外発生時に実行される

  puts $!                       # 例外メッセージを出力

  file = "/etc/host.conf"       # 実在するファイルを指定

  retry                         # begin節を再実行

ensure  # begin節を抜ける直前に実行される

  f.close

end

上記の例ではあるファイルをオープンしようとして例外(file not found)が発生し、再度rescue節にて指定した実在するファイルをオープンしている。

これを実行すると以下のような結果になる。

No such file or directory - ./non_exist
opened => /etc/host.conf

2007/8/21更新

対応バージョン: 1.8

クラス/メソッドの定義方法

Rubyのクラス/メソッドは以下のように簡単に記述できる。

例)

まずクラス/メソッド定義を「myclass.rb」として作成する。

class Dog		# 犬に関するクラス定義
  def type		# 種別メソッド
    return "犬"
  end

  def voice		# 鳴き声メソッド
    return "わんわん"
  end
end

class Cat		# 犬に関するクラス定義
  def type		# 種別メソッド
    return "猫"
  end

  def voice		# 鳴き声メソッド
    return "にゃー"
  end
end

これを使って以下のような処理が書ける。

require "myclass"

pochi = Dog.new
mike  = Cat.new

puts "#{pochi.type}は「#{pochi.voice}」と鳴く"
puts "#{mike.type}は「#{mike.voice}」と鳴く"

=> 犬は「わんわん」と鳴く
=> 猫は「にゃー」と鳴く

メソッドから複数の戻り値を返す

通常メソッドの戻り値は一つしか指定できないが、配列を使うことによって複数の戻り値を返すことができる。

return 1, 2, 3

これは以下の書き方と同じ意味になる。

data = [1, 2, 3]
return data

メソッド呼び出し側ではこの戻り値をリストや多重代入により取り出すことができる。

def func
  data = [1,2,3]
  return data
end

# リスト
ret = func

p ret
=> [1, 2, 3]

# 多重代入
ret1, ret2, ret3 = func

p ret1,ret2,ret3
=> 1
=> 2
=> 3

メソッドの引数のデフォルト値を設定する

メソッドの引数に対して、何も指定されなかった場合のデフォルト値を指定することができる。

def func(data = "none")
  return data
end

p func("foo")
=> "foo"

p func()
=> "none"

2008/8/16更新

対応バージョン: 1.8

要素数取得

lengthメソッド、あるいはsizeメソッドを使用する。

どちらも動作に違いはないが、要素数を「長さ」で扱うか「大きさ」で扱うかによって目的別に使い分けるとソースコードが読みやすくなる。

例)

data = [5,1,2,4,3]

p data.length

=> 5

p data.size

=> 5

要素追加

要素を追加する場所により使用するメソッドが異なる。

pushメソッド (<<演算子も使用可能)

配列の最後に追加(一要素)

concatメソッド

配列の最後に追加(複数要素)

unshiftメソッド

配列の先頭に追加(一要素)

[]演算子

配列の先頭に追加(複数要素)

例)

data = [5,1,2,4,3]

data.push(8)   # data << 8 と同等

p data

=> [5, 1, 2, 4, 3, 8]

data.concat([9, 10])

p data

=> [5, 1, 2, 4, 3, 8, 9, 10]

data.unshift(0)

p data

=> [0, 5, 1, 2, 4, 3, 8, 9, 10]

data[0, 0] = [6, 7]

p data

=> [6, 7, 0, 5, 1, 2, 4, 3, 8, 9, 10]

要素削除

追加と同様、要素を削除する場所により使用するメソッドが異なる。

popメソッド

配列の最後から削除(一要素)

shiftメソッド

配列の先頭から削除(一要素)

delete_atメソッド

配列の任意の場所から削除(一要素)

delete_ifメソッド

配列から指定条件を満たす要素を削除(複数要素)

slice!メソッド

配列の任意の場所から削除(複数要素)

(*) sliceメソッドでは要素は取り出せるが元配列からは取り除かれない

例)

data = [5,1,2,4,3]

data.pop

p data

=> [5, 1, 2, 4]

data.shift

p data

=> [1, 2, 4]

data.delete_at(1)           # 位置1の要素を削除

p data

=> [1, 4]

data = [5,1,2,4,3]

data.delete_if {|x| x < 3}  # 値が3未満の要素を削除

p data

=> [5, 4, 3]

data = [5,1,2,4,3]

data.slice!(1, 2)           # 位置1から2要素分を削除

p data

=> [5, 4, 3]

重複要素除去

uniqメソッドを使用する。

ただし、UNIXのuniqコマンドと違って隣り合っていない重複要素も取り除くので注意が必要である。

例)

data = [5,2,1,2,4,3,2]	# 2が3回重複

p data.uniq

=> [5, 2, 1, 4, 3]

配列の一部取得

[]メソッド、sliceメソッドを使用する。

例)

data = [1,2,3,4,5]

p data[1,2]     # 要素番号1から2要素取り出す(= data.slice(1,2))

=> [2, 3]

p data[2..3]    # 要素番号2〜3を取り出す(= data.slice(2..3))

=> [3, 4]

p data[2..-1]   # 要素番号2から最終要素までを取り出す(= data.slice(2..-1))

=>[3, 4, 5]

条件を満たす要素取得

条件を満たす最初の要素だけを取り出す場合はfindメソッドかdetectメソッドを、全ての要素を取り出す場合はselectメソッドかfind_allメソッドを使用する。

例) 3の倍数を取り出す

data = [1,2,3,4,5,6,7,8,9,10]

out = data.find {|item| item % 3 == 0}
p out

=> 3

out = data.select {|item| item % 3 == 0}
p out

=> [3, 6, 9]

ソート

配列をソートするにはsortメソッドを使用する。

逆順にソートするメソッドはないのでいったんsortでソートしておいてreverseメソッドによって配列を逆順に並べ替える。

(*) reverseは配列の要素を逆順に並べ替えるだけで、逆順にソートするわけではない。

例)

data = [5,1,2,4,3]

p data.sort             # ソート

=> [1,2,3,4,5]

p data.sort.reverse     # 逆順ソート

=> [5,4,3,2,1]

p data.reverse          # 逆順に並び替える

=> [3,4,2,1,5]

引数として引き渡す/受け取る

メソッド呼び出し時に配列を引き渡す、あるいはメソッド定義において引数を配列で受け取る場合、配列名の先頭に「*」を付ける。

例)

def func(*args)
  args.sort.each {|arg|
    puts arg
  }
end

data = [3, 1, 2]

func(*data)

=> 1
=> 2
=> 3