1.8.1 より後の Ruby では getopts は非推奨になった模様。
Rubyリファレンスマニュアル - getopts.rb
http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=geto ...
http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-lis ...
[ruby-list:40502] Re: getopt
http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-lis ...
2004-11-01 などで書いた「添付ファイル付きメール送信スクリプト」の samail で使ってるのって何だったっけ?
あ、getopts だ。ダメじゃん。そのうち直そう。samail を書くときに困ったのは、どのモジュールが定番なのかよくわからなかったことだ。文字コード変換にも kconv や nkf とかいくつかあって、何を使うのが良いかを調べきれなかった。とりあえず標準で使えて、機能的に要件を満たしてるなら何でもいいやと思って kconv を使うことにしたんだっけ。
そういえば、2004-12-06 の 「sourceforge.jp にプロジェクトを登録する」で書いた samail を sourceforge に持って行く作業も滞ってるなー。なんとか時間作って作業しなきゃ。
非推奨になったことを Rubyリファレンスマニュアル - getopts.rb http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=geto ... に書いた方が良いと思うけど、どんな体裁で書いたらいいかよくわからないな。個人の wiki じゃなくてリファレンスマニュアルなのでテキトーに直すというのは良くなさそう。正しく整然と書きたい。
Rubyリファレンスマニュアル - 執筆者募集 http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=%BC% ... からリンクが張られてた The RWiki - Manuals' style http://pub.cozmixng.org/~the-rwiki/rw-cgi.rb?cmd=view&na ... には ML などに問い合わせてねとあるので、そのうち投稿してみるか。
Rubyリファレンスマニュアル - getopts.rb
http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=geto ...
オプションを解析し、$OPT_xxx に値を設定します。
- 開発版の Ruby 1.9系 で getopts の使用に警告が出る
[ruby-list:40501] getopthttp://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-lis ...
これは, 1.9系で発生するのですが...
# メッセージから言うと 1.8.2でもですかね...
emperor% ruby -e "require 'getopts'"
Warning:-e:1: getopts is deprecated after Ruby 1.8.1; use optparse instead
[ruby-list:40502] Re: getopt
http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-lis ...
|emperor% ruby -e "require 'getopts'"
|Warning:-e:1: getopts is deprecated after Ruby 1.8.1; use optparse instead
|
|getopts.rb は非推奨品になったということでしょうか?
そういうことです。
|とはいえ, optparse って随分インターフェイスが変わっていて戸惑ってしま
|うのですが...
とはいえ、グローバル変数を多用したgetoptsを今さら推奨するっ
てのもねえ。
|あと, getoptlong.rb もあると思いますが, それも推薦品ではないってことで
|しょうか?
そうですね。どっちかといえばoptparseを勧めたいって感じです。
2004-11-01 などで書いた「添付ファイル付きメール送信スクリプト」の samail で使ってるのって何だったっけ?
#!/usr/local/bin/ruby
# Copyright (C) 2004 Saito Hiroaki <sonic64@infoseek.jp>
# http://sonic64.com/
require 'kconv'
require 'net/smtp'
require 'getopts'
あ、getopts だ。ダメじゃん。そのうち直そう。samail を書くときに困ったのは、どのモジュールが定番なのかよくわからなかったことだ。文字コード変換にも kconv や nkf とかいくつかあって、何を使うのが良いかを調べきれなかった。とりあえず標準で使えて、機能的に要件を満たしてるなら何でもいいやと思って kconv を使うことにしたんだっけ。
そういえば、2004-12-06 の 「sourceforge.jp にプロジェクトを登録する」で書いた samail を sourceforge に持って行く作業も滞ってるなー。なんとか時間作って作業しなきゃ。
非推奨になったことを Rubyリファレンスマニュアル - getopts.rb http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=geto ... に書いた方が良いと思うけど、どんな体裁で書いたらいいかよくわからないな。個人の wiki じゃなくてリファレンスマニュアルなのでテキトーに直すというのは良くなさそう。正しく整然と書きたい。
Rubyリファレンスマニュアル - 執筆者募集 http://www.ruby-lang.org/ja/man/index.cgi?cmd=view;name=%BC% ... からリンクが張られてた The RWiki - Manuals' style http://pub.cozmixng.org/~the-rwiki/rw-cgi.rb?cmd=view&na ... には ML などに問い合わせてねとあるので、そのうち投稿してみるか。
2004-10-09 で書いた「添付ファイル付メールを送信する Ruby スクリプト」である samail 0.3 を修正して バージョン 0.4 とした。
・状況表示メッセージは標準エラー出力に出力するようにした。
・--output - を指定されたときは、SMTP にメールを投げずに標準出力のみに出力するようにした。
・setup メソッド内で初期化した変数を他のメソッドで参照するのはやっぱり違和感があるので setup メソッドを削除。グローバルなんだからいいんだろうけど。
ローカルの sendmail コマンドに samail の出力を渡す例。
- samail 0.4 の修正点
・smtp タイムアウト時のエラーを rescue Exception で捕捉するようにした。・状況表示メッセージは標準エラー出力に出力するようにした。
・--output - を指定されたときは、SMTP にメールを投げずに標準出力のみに出力するようにした。
・setup メソッド内で初期化した変数を他のメソッドで参照するのはやっぱり違和感があるので setup メソッドを削除。グローバルなんだからいいんだろうけど。
- samail 0.4 で便利になった点
標準出力にメールの中身を出力するようにしたので、ローカルのメール送信コマンドを利用することができるようになった。これにより、samail の簡易な SMTP リトライ機能ではなくローカルの MTA が提供する強力かつ信頼性の高いリトライ機能を利用できる。また、samail はリトライのみでキューイング機能を持たないが、ローカル MTA はキューイング機能を持っていればそれを利用できる。ローカルの sendmail コマンドに samail の出力を渡す例。
$ samail -v --to "sonic64@infoseek.jp example@example.com" --from sonic64@infoseek.jp --attachment "logo.png /tmp/backup.tar.bz2" |/usr/sbin/sendmail -f sonic64@infoseek.jp -oi "sonic64@infoseek.jp example@example.com"
- samil 0.4 ソース
ライセンスは 2004-10-09 でも書いたとおり、GPL2 と BSD のデュアルライセンス。#!/usr/local/bin/ruby
# Copyright (C) 2004 Saito Hiroaki <sonic64@infoseek.jp>
# http://sonic64.com/
require 'kconv'
require 'net/smtp'
require 'getopts'
APPLICATION_NAME = 'Landscape Mailsender'
APPLICATION_VERSION = '0.4'
BANNER_STRING = APPLICATION_NAME + ' ' + APPLICATION_VERSION
X_MAILER_WEBSITE = 'http://sonic64.com/2004-11-01.html'
def build_mail_header()
$mail_content << 'Date: ' + Time::now.strftime("%a, %d %b %Y %X %z") + "\n"
$mail_content << 'From: ' + $mail_from + "\n"
$mail_content << 'To: ' + $mail_to.join(', ') + "\n"
$mail_content << 'Subject: ' + $mail_subject + "\n"
$mail_content << "MIME-Version: 1.0\n"
$mail_content << 'Content-Type: multipart/mixed; boundary="' + $boundary + "\"\n"
$mail_content << 'X-Mailer: ' + BANNER_STRING + "\n"
$mail_content << 'X-Mailer-WebSite: ' + X_MAILER_WEBSITE + "\n"
$mail_content << "\n"
end
def add_attchment(file_name)
if file_name == '' then
return
end
attachment_file = File.open(file_name).readlines.join('')
encoded_attachment = [attachment_file].pack('m')
$mail_content << '--' + $boundary + "\n"
$mail_content << "Content-Type: application/octet-stream;\n"
$mail_content << ' name="' + File.basename(file_name) + '"' + "\n"
$mail_content << "Content-Transfer-Encoding: base64\n"
$mail_content << "Content-Disposition: attachment;\n"
$mail_content << ' filename="' + File.basename(file_name) + "\"\n\n"
$mail_content << encoded_attachment + "\n"
end
def send_mail(retry_count)
begin
STDERR.puts Time::now.to_s + " Try to connect " + $smtp_server if $OPT_v
Net::SMTP.start($smtp_server) do |smtp|
smtp.sendmail($mail_content, $mail_from, $mail_to)
end
STDERR.puts Time::now.to_s + ' Send OK' if $OPT_v
rescue Exception => e
STDERR.puts Time::now.to_s + ' smtp error: ' + e.message
if retry_count != 0 then
retry_count -= 1
STDERR.puts Time::now.to_s + ' sleep: ' + $smtp_retry_interval_second.to_s + 'sec'
sleep($smtp_retry_interval_second)
$smtp_retry_interval_second *= 2
retry
else
STDERR.puts Time::now.to_s + ' abort: Over retry count'
raise
end
end
end
# main
usage_message = "usage: #$0 [-v] --to TO_EMAIL_ADDRESS [--from FROM_EMAIL_ADDRESS] [--smtp SMTP_SERVER] [--attachment ATTACHMENT_FILE] [--output]"
unless getopts('v', 'from:', 'to:', 'subject:', 'attachment:', 'smtp:', 'output:')
abort usage_message
end
abort "#$0: specifiy --to TO_EMAIL_ADDRESS\n" + usage_message if ! $OPT_to
if $OPT_output == '-' then
$output2stdout = true
$smtp_server = ''
elsif $OPT_smtp then
$output2stdout = false
$smtp_server = $OPT_smtp
else
$smtp_server = 'localhost'
end
$mail_from = $OPT_from ? $OPT_from : ENV['USER'] + '@' + ENV['HOSTNAME']
$mail_to = $OPT_to.split(/,\s*|\s+/)
$mail_subject = $OPT_subject ? $OPT_subject : APPLICATION_NAME + ' [' + Time::now.strftime("%a, %d %b %Y %X %z") + ']'
$mail_content = ''
$boundary = 'boundary_string_by_landscape_mailsender'
$attachment_file = $OPT_attachment ? $OPT_attachment.split(/,\s*|\s+/) : []
$smtp_retry = 8
$smtp_retry_interval_second = 30
if $OPT_v then
STDERR.puts 'To: ' + $mail_to.join(', ')
STDERR.puts 'From: ' + $mail_from
STDERR.puts 'Attachment file: ' + $attachment_file.join(", ")
STDERR.puts 'Subject: ' + $mail_subject
if $output2stdout then
STDERR.puts 'Output to STDOUT'
else
STDERR.puts 'SMTP Server: ' + $smtp_server
end
end
build_mail_header()
$mail_content << '--' + $boundary + "\n"
$mail_content << "Content-Type: text/plain; charset=iso-2022-jp\n"
$mail_content << "Content-Transfer-Encoding: 7bit\n"
$mail_content << "\n"
$mail_content << "-- \n"
$mail_content << 'Powered by ' + BANNER_STRING + "\n"
$mail_content << X_MAILER_WEBSITE + "\n"
$mail_content << "\n"
$attachment_file.each do |file|
add_attchment(file)
end
$mail_content << '--' + $boundary + "--\n"
if $output2stdout then
puts $mail_content
else
send_mail($smtp_retry)
end
rescue Exception とすればあらゆる例外を rescue で捕捉できる。
表題の catch は Ruby の 多重ループを抜けるときなどに使う catch のことではなく、「例外を捕捉する」という意味で書いた。
[ruby-list:35487] Re: [ 質問] 例外の補足について
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/ ...
2004-10-09 で書いた 「添付ファイル付メールを送信する Ruby スクリプト」の samail 0.3 には例外発生時のリトライ機能を付けたのだが、それが機能していない。動作させたときのログを確認すると、Timeout::Error を rescue できていなかった。
C# の try catch や delphi の try exception では rescue Exception の動作がデフォルト。Ruby も同じようなものと思いこんでた。Ruby のリファレンスを読んでおけば良かったな。
表題の catch は Ruby の 多重ループを抜けるときなどに使う catch のことではなく、「例外を捕捉する」という意味で書いた。
[ruby-list:35487] Re: [ 質問] 例外の補足について
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/ ...
| begin - rescue - end による例外の補足機能では,補足できない例外があ
|るのでしょうか?
rescueに捕捉する例外を指定しないとStandardError(とそのサブク
ラス)を指定したと見なされます。例外の中にはStandardErrorのサ
ブクラスで無いものもありますから(たとえば、1.6ではNameError
はそうではありません)、その場合には捕捉できません。
具体的に例外を指定するか、
rescue Exception
とするのが対処方法です。Exceptionを指定してあらゆる例外を捕
捉した場合には、思わぬ例外まで捕捉してしまわないように気をつ
けてください。
2004-10-09 で書いた 「添付ファイル付メールを送信する Ruby スクリプト」の samail 0.3 には例外発生時のリトライ機能を付けたのだが、それが機能していない。動作させたときのログを確認すると、Timeout::Error を rescue できていなかった。
C# の try catch や delphi の try exception では rescue Exception の動作がデフォルト。Ruby も同じようなものと思いこんでた。Ruby のリファレンスを読んでおけば良かったな。
コマンドラインから添付ファイル付きメールを送信する Ruby スクリプト samail を作成した。Send Attachment MAIL の略で、samail。読み方は「さめいる」または「えすえーめいる」かなあ。
2003-12-12 の「添付ファイル付きメールをコマンドラインから送信」や 2004-07-02 の「添付ファイル付メールを送信する Perl/Rubyスクリプト」 でもいろいろ書いたけど、Ruby 1.8 が使えるなら今回書いたスクリプトの方がカスタマイズはしやすい。
Ruby 1.8 の標準ライブラリしか使っていないため、Ruby さえインストールしてあれば動作する。
To, From, Subject を指定可能。To には複数のアドレスを指定可能。
Cc と Bcc には未対応。
SMTP サーバが必要。ローカルの MDA を呼び出す機能は未実装。
SMTP 失敗時には自動的にリトライする。初期設定では3回までリトライ。
ruby 1.8.1 (2003-12-25) [i386-cygwin]
ファイルの文字コードは ASCII しか使ってないので何でも良い。改行コードは LF が良い。
chmod 744 などどして、ファイル samail に実行権限を付与。
以上で完了。
to や attachment には複数の値を指定できる。その場合、ダブルクオートかシングルクオートで値をくくり、それぞれの値はカンマかスペースで区切る。
-v オプションを指定していると、標準出力に To, From, 添付ファイルのパス, subject, 使用する SMTP サーバ名を表示する。"Try to connect ..." はメール本文の生成が完了し、SMTP サーバに接続しようとした時点で表示する。Send OK は SMTP サーバへの送信を完了したときに表示する。
samail は X-Mailer などで Landscape Mailsender を名乗る。
http://www.loveruby.net/ja/prog/tmail.html
vCard ファイルを添付したメールを作る
http://namazu.org/~satoru/attic/vcardmail.rb
SSTP Bottle メール送信クライアント
http://www.tenchi.ne.jp/~yoko/haruna/
2003-12-12 の「添付ファイル付きメールをコマンドラインから送信」や 2004-07-02 の「添付ファイル付メールを送信する Perl/Rubyスクリプト」 でもいろいろ書いたけど、Ruby 1.8 が使えるなら今回書いたスクリプトの方がカスタマイズはしやすい。
- samail の概要
コマンドラインで動作する、SMTP を利用したメール送信プログラム。Ruby 1.8 の標準ライブラリしか使っていないため、Ruby さえインストールしてあれば動作する。
- samail の機能
添付ファイルを好きな数だけ指定して送信できる。To, From, Subject を指定可能。To には複数のアドレスを指定可能。
Cc と Bcc には未対応。
SMTP サーバが必要。ローカルの MDA を呼び出す機能は未実装。
SMTP 失敗時には自動的にリトライする。初期設定では3回までリトライ。
- samail の動作確認環境
ruby 1.8.1 (2003-12-25) [i586-linux]ruby 1.8.1 (2003-12-25) [i386-cygwin]
- samil のインストール
後述する samail ソースをコピー & ペーストして、samail というファイル名を付けてパスの通ったディレクトリに保存。ファイルの文字コードは ASCII しか使ってないので何でも良い。改行コードは LF が良い。
chmod 744 などどして、ファイル samail に実行権限を付与。
以上で完了。
- samail の使い方と動作例
以下のようにコマンドラインから必要なオプションを渡す。to や attachment には複数の値を指定できる。その場合、ダブルクオートかシングルクオートで値をくくり、それぞれの値はカンマかスペースで区切る。
$ samail -v --to "sonic64@infoseek.jp example@example.com" --from sonic64@infoseek.jp --smtp smtp.example.com --attachment "logo.png /tmp/backup.tar.bz2"
-v オプションを指定していると、標準出力に To, From, 添付ファイルのパス, subject, 使用する SMTP サーバ名を表示する。"Try to connect ..." はメール本文の生成が完了し、SMTP サーバに接続しようとした時点で表示する。Send OK は SMTP サーバへの送信を完了したときに表示する。
To: sonic64@infoseek.jp, example@example.com
From: sonic64@infoseek.jp
Attachment file: logo.png, /tmp/backup.tar.bz2
Subject: Landscape Mailsender Sat, 09 Oct 2004 20:35:34 +0900]
SMTP Server: smtp.example.com
Try to connect smtp.example.com
Send OK
samail は X-Mailer などで Landscape Mailsender を名乗る。
- ライセンス
何が良いんだろう? とりあえず GPL2 と BSD のデュアルライセンスにしておけばいいのかな。- samail ソース
#!/usr/local/bin/ruby
# Copyright (C) 2004 Saito Hiroaki <sonic64@infoseek.jp>
# http://sonic64.com/
require 'kconv'
require 'net/smtp'
require 'getopts'
def setup()
$APPLICATION_NAME = 'Landscape Mailsender'
$VERSION = '0.3'
$BANNER_STRING = $APPLICATION_NAME + ' ' + $VERSION
$X_MAILER_WEBSITE = "http://sonic64.com/2004-10-09.html"
$smtp_server = $OPT_smtp? $OPT_smtp : 'localhost'
$mail_from = $OPT_from ? $OPT_from : ENV['USER'] + '@' + ENV['HOSTNAME']
$mail_to = $OPT_to.split(/,\s*|\s+/)
$mail_subject = $OPT_subject ? $OPT_subject : $APPLICATION_NAME + ' [' + Time::now.strftime("%a, %d %b %Y %X %z") + ']'
$mail_content = ''
$boundary = 'boundary_string_by_landscape_mail'
$attachment_file = $OPT_attachment ? $OPT_attachment.split(/,\s*|\s+/) : []
$smtp_retry = 3
if ($OPT_v) then
puts 'To: ' + $mail_to.join(', ')
puts 'From: ' + $mail_from
puts 'Attachment file: ' + $attachment_file.join(", ")
puts 'Subject: ' + $mail_subject
puts 'SMTP Server: ' + $smtp_server
end
end
def build_mail_header()
$mail_content << 'Date: ' + Time::now.strftime("%a, %d %b %Y %X %z") + "\n"
$mail_content << 'From: ' + $mail_from + "\n"
$mail_content << 'To: ' + $mail_to.join(', ') + "\n"
$mail_content << 'Subject: ' + $mail_subject + "\n"
$mail_content << "MIME-Version: 1.0\n"
$mail_content << 'Content-Type: multipart/mixed; boundary="' + $boundary + "\"\n"
$mail_content << 'X-Mailer: ' + $BANNER_STRING + "\n"
$mail_content << 'X-Mailer-WebSite: ' + $X_MAILER_WEBSITE + "\n"
$mail_content << "\n"
end
def add_attchment(file_name)
if file_name == '' then
return
end
attachment_file = File.open(file_name).readlines.join('')
encoded_attachment = [attachment_file].pack('m')
$mail_content << '--' + $boundary + "\n"
$mail_content << "Content-Type: application/octet-stream;\n"
$mail_content << ' name="' + File.basename(file_name) + '"' + "\n"
$mail_content << 'Content-Transfer-Encoding: base64' + "\n"
$mail_content << "Content-Disposition: attachment;\n"
$mail_content << ' filename="' + File.basename(file_name) + "\"\n\n"
$mail_content << encoded_attachment + "\n"
end
def send_mail(retry_count)
begin
puts "Try to connect " + $smtp_server if $OPT_v
Net::SMTP.start($smtp_server) do |smtp|
smtp.sendmail($mail_content, $mail_from, $mail_to)
end
puts 'Send OK' if $OPT_v
rescue
puts 'smtp error occurred'
if retry_count != 0 then
retry_count -= 1
send_mail(retry_count)
else
puts 'abort: Over retry count'
raise
end
end
end
# main
unless getopts('v', 'from:', 'to:', 'subject:', 'attachment:', 'smtp:')
abort "usage: #$0 [-v] --to TO_EMAIL_ADDRESS [--from FROM_EMAIL_ADDRESS] [--smtp SMTP_SERVER] [--attachment ATTACHMENT_FILE]"
end
setup()
build_mail_header()
$mail_content << '--' + $boundary + "\n"
$mail_content << "Content-Type: text/plain; charset=iso-2022-jp\n"
$mail_content << "Content-Transfer-Encoding: 7bit\n"
$mail_content << "\n"
$mail_content << "-- \n"
$mail_content << 'Powered by ' + $BANNER_STRING + "\n"
$mail_content << $X_MAILER_WEBSITE + "\n"
$mail_content << "\n"
$attachment_file.each do |file|
add_attchment(file)
end
$mail_content << '--' + $boundary + "--\n"
send_mail($smtp_retry)
- 参考にしたもの
TMailhttp://www.loveruby.net/ja/prog/tmail.html
vCard ファイルを添付したメールを作る
http://namazu.org/~satoru/attic/vcardmail.rb
SSTP Bottle メール送信クライアント
http://www.tenchi.ne.jp/~yoko/haruna/
- 2004年11月1日追記 2004-11-01 に samail 0.4 について書いた。
コマンドラインから添付ファイル付きメールを送信したい。cron で定期的にメール送信するバッチが必要になった。
エラーを追求する手間をかけるよりも、今回は他の解決策を探した方が良いと私のゴーストがささやいている。「From の指定」など、mpack には無い機能も使いたいし。というわけで、今回は Perl か Ruby で SMTP と Content-Type: multipart/mixed; を扱うスクリプトを書くことにしよう。
ActivePerlからメールを送る
http://member.nifty.ne.jp/hippo2000/perltips/perlmail.htm
さすがは Perl、全くスクリプトを書かなくてもやりたいことを達成できてしまった。
Google で ruby 添付ファイル メール 送信を検索。なんだかあまりヒットしないな。こういう細かい仕事を自動化するスクリプトの需要は結構あると思うんだけどな。検索語が悪いのかも。英語でやってみよう。Google で ruby base64 mail attachment を検索。[ruby-list:30312] composing a mail with a big file attached がヒット。なるほど、Tmail という便利なライブラリがあるんだね。
TMail
http://www.loveruby.net/ja/prog/tmail.html
オフィシャルサイトの説明には「Ruby 用 メール総合ライブラリ」って書いてある。かなり高機能なライブラリのようだ。あ、Tmail の作者は 「Rubyソースコード完全解説」や「Ruby レシピブック」で有名な青木さんだ。
tar を展開して、
setup.rb config
setup.rb setup
setup.rb install
を実行するだけ。
コンパイルの時にいろいろエラーがでたけど、セットアップスクリプトが ignore するよって言ってるんだから気にしないことにする。
さて、この便利なライブラリ Tmail を使えば、簡単に添付ファイル付きメール送信スクリプトが書けるだろう。でも、やっぱりだれかが同じようなスクリプトを書いているはず。それを探して使うほうが早いだろう。ウェブを探してみると、高林さんが書いた「vCard ファイルを添付したメールを作る」が Tmail を使っている。短いし、これをちょっと修正すればやりたいことは達成できそう。
vCard ファイルを添付したメールを作る
http://namazu.org/~satoru/attic/vcardmail.rb
- 2003-12-12 でも同じ記事を書いたけど
2003-12-12 にも同じ「添付ファイル付きメールをコマンドラインから送信」という記事を書いたが、あれは Linux での話だ。今回は cygwin で同じことをやりたい。本当は前回の記事でも使った mpack を cygwin 環境でコンパイルして使おうと思ったんだけど、コンパイルエラーとなってしまった。エラーを追求する手間をかけるよりも、今回は他の解決策を探した方が良いと私のゴーストがささやいている。「From の指定」など、mpack には無い機能も使いたいし。というわけで、今回は Perl か Ruby で SMTP と Content-Type: multipart/mixed; を扱うスクリプトを書くことにしよう。
- Perl で添付ファイル付きメールを送信
Perl であれば、すでに添付ファイル付きメール送信スクリプトのサンプルが河馬屋二千年堂のウェブサイトにあったはずなので、それを使えばいい。このサンプルを動かすには、いくつか必要なモジュールがある。その部分だけ引用しておく。ActivePerlからメールを送る
http://member.nifty.ne.jp/hippo2000/perltips/perlmail.htm
添付ファイル付のメールを送信する + 日本語への対応+HTMLメール
use Net::SMTP;
use MIME::Entity;
use MIME::Words qw (:all);
require 'jcode.pl';
さすがは Perl、全くスクリプトを書かなくてもやりたいことを達成できてしまった。
- RUby で添付ファイル付きメールを送信
Ruby は標準インストールされるライブラリが充実してるから、もしかしたら smtp や base64 を扱うクラス、果ては添付ファイル付きメール送信クラスなんてものまで標準で用意されてるかも。ちょっと期待。Google で ruby 添付ファイル メール 送信を検索。なんだかあまりヒットしないな。こういう細かい仕事を自動化するスクリプトの需要は結構あると思うんだけどな。検索語が悪いのかも。英語でやってみよう。Google で ruby base64 mail attachment を検索。[ruby-list:30312] composing a mail with a big file attached がヒット。なるほど、Tmail という便利なライブラリがあるんだね。
TMail
http://www.loveruby.net/ja/prog/tmail.html
オフィシャルサイトの説明には「Ruby 用 メール総合ライブラリ」って書いてある。かなり高機能なライブラリのようだ。あ、Tmail の作者は 「Rubyソースコード完全解説」や「Ruby レシピブック」で有名な青木さんだ。
- Tmail のインストール
cygwin でも無事インストールできた。tar を展開して、
setup.rb config
setup.rb setup
setup.rb install
を実行するだけ。
コンパイルの時にいろいろエラーがでたけど、セットアップスクリプトが ignore するよって言ってるんだから気にしないことにする。
さて、この便利なライブラリ Tmail を使えば、簡単に添付ファイル付きメール送信スクリプトが書けるだろう。でも、やっぱりだれかが同じようなスクリプトを書いているはず。それを探して使うほうが早いだろう。ウェブを探してみると、高林さんが書いた「vCard ファイルを添付したメールを作る」が Tmail を使っている。短いし、これをちょっと修正すればやりたいことは達成できそう。
vCard ファイルを添付したメールを作る
http://namazu.org/~satoru/attic/vcardmail.rb
- スクリプト完成、いざメール送信
高林さんのスクリプトほとんどそのまま。元のスクリプトが GPL2 なので、以下のスクリプトのライセンスも GPL2。#!/usr/bin/env ruby
require 'kconv'
require 'tmail'
require 'net/smtp'
def generate_filename (vcard)
/^FN:(.*)/ =~ vcard
name = $1
"=?ISO-2022-JP?B?" + (name + '.vcf').tojis.to_a.pack('m').chomp + "?="
end
raise unless ARGV.length == 2
to = ARGV.shift
vcard = File.open(ARGV.shift).readlines.join('')
mail = TMail::Mail.new
mail.to = to
mail.from = 'example@example.com'
mail.subject = "vcard"
mail.date = Time.now
mail.mime_version = '1.0'
message = TMail::Mail.new
message.set_content_type('text', 'plain')
message.transfer_encoding = '7bit'
message.body = "vcard is attached.\n"
filename = 'atch'
encoded_vcard = [vcard].pack('m').chomp.gsub(/.{76}/, "\\1\n")
attachment = TMail::Mail.new
attachment.body = encoded_vcard
attachment.transfer_encoding = 'base64'
attachment.set_content_type('text', 'x-vcard', 'name' => filename)
attachment.set_content_disposition('attachment',
'filename' => filename)
mail.parts.push(message)
mail.parts.push(attachment)
mail.write_back
$smtp_server = '10.83.0.38'
Net::SMTP.start($smtp_server) do |smtp|
smtp.sendmail(mail.encoded, mail.from, to)
end
- 2004年7月7日追記
って、あれ? この記事はもう少し加筆してから公開しようかと思ってたんだけど、いつの間にか公開状態になってる。まあいいや。上記スクリプトも変数名を直したり、複数添付ファイルに対応しようと思ってた。もしかしたら全く動かないかもしれない。そのうち直す予定。- 2004-10-09 追記
Ruby 1.8 の標準ライブラリだけで動作する添付ファイル付きメール送信スクリプトを 2004-10-09 に書いた。著者の一人である青木さんの「あおきにっき つっこみつき」に情報があった。
http://i.loveruby.net/d/20040512.html#p02
『Rubyレシピブック』 2800 円で 5 月 28 日配本だそうです。配本つーことは、本屋にはもうちょい後にならぶ?あ、31 日出版となってるな。
とにかくそのあたりです!
Google で Ruby レシピブックを検索してもほとんど情報が無い。amazon や bk1 のデータベースにもまだ登録されていないようだ。たぶん上の紹介リンクも置換前の文字列のままだろう。データが登録されれば表示されるようになるだろうけど。唯一情報があったのが、ソフトバンクパブリッシングのサイト。
SBPストア Rubyレシピブック 268の技
http://store.sbpnet.jp/bm_detail.asp?sku=4797324295
RubyのTipsを満載したレシピ集。Rubyを使いこなすうえでのさまざまなテクニックを幅広く解説。様々な難問を即解決!
現時点では詳細な内容や目次の情報がほとんど無いが、Perl クックブックのようなノウハウ集だと私は想像している。Ruby はあまり慣れていないので、ぜひ手元に置いておきたい。2800円という値段も手頃だし。
- 2004年5月17日追記
あおきにっきつっこみつきに追加情報があった。『Rubyレシピブック』内容紹介
http://i.loveruby.net/d/20040516.html#p04
Ruby で DB を扱う必要が出てきたので環境整備。本当を MySQL を使いたいのだが、サーバに MySQL が入っていない。使い慣れた PostgreSQL でもいいかな、と思ったので Postgres 環境を整備。
Google で ruby postgres を検索したらヒット。
Postgres(Ruby PostgreSQL 拡張モジュール)
http://www.postgresql.jp/interfaces/ruby/index-ja.html
テストデータを100件 INSERT。100 回 psql を呼んでるので遅いけど気にしない。
SELECT 文を投げて、レコードの件数をカウント。この程度ならワンライナーで書いた方が早い。
よしよし、ばっちり接続できてるね。
- Postgres 接続用ライブラリはいずこに?
/usr/local/lib/ruby を探してみたが、postgres 関連のライブラリは無いようだ。Google で ruby postgres を検索したらヒット。
Postgres(Ruby PostgreSQL 拡張モジュール)
http://www.postgresql.jp/interfaces/ruby/index-ja.html
- インストール
ruby-postgres-0.7.1.tar.gz をダウンロードして、展開。ruby extconf.rb --with-pgsql-include-dir=/usr/local/pgsql/include --with-pgsql-lib-dir=/usr/local/pgsql/libで configure。あとは make して make install でインストール完了。実に簡単。
- ruby から接続できるかテスト
テストテーブルを作成。$ psql -c 'create table test1 (num int);'
テストデータを100件 INSERT。100 回 psql を呼んでるので遅いけど気にしない。
$ for i in `seq 1 100`; do psql -c "INSERT INTO test1 (num) VALUES ($i);"; done
SELECT 文を投げて、レコードの件数をカウント。この程度ならワンライナーで書いた方が早い。
$ ruby -e 'require "postgres"; conn = PGconn.connect("localhost", 5432, "", "", "hiroaki"); res = conn.exec("select * from test1;"); p res.num_tuples;'
100
よしよし、ばっちり接続できてるね。
- 今回作業した環境
$ ruby -v
ruby 1.8.1 (2003-12-25) [i586-linux]
$ psql -V
psql (PostgreSQL) 7.2.1
FileTest::file? や同様のメソッドである File.file? にはフルパスを渡すこと。ファイル名だけの場合はカレントディレクトリがパスとして渡される。
以下のコードを /home/hiroaki で実行すると、Dir::foreach は /home/hiroaki/public_html/log にあるファイルを返すが、その後の ファイルテストでは ファイル名しか渡っていないので、カレントディレクトリが指定されたことになり、File.file? 結果はすべて false になる。
こうすれば OK。
Dir::foreach のサンプルコードを載せてるサイトもいくつか見てみたんだけど、みんな Dir::foreach('.') と書いていたので気づくのが遅れた。
ruby でワンライナーを書くとセミコロンを忘れてエラーになっちゃうことが多いな。perl だったら普段からセミコロンをつけるから忘れないんだけど。
以下のコードを /home/hiroaki で実行すると、Dir::foreach は /home/hiroaki/public_html/log にあるファイルを返すが、その後の ファイルテストでは ファイル名しか渡っていないので、カレントディレクトリが指定されたことになり、File.file? 結果はすべて false になる。
$ ruby -v; ruby -e 'target = "/home/hiroaki/public_html/log"; Dir::foreach(target) {|f| if File.file?(f) then p "file: " + f end }'
ruby 1.8.1 (2003-12-25) [i386-cygwin]
こうすれば OK。
$ ruby -v; ruby -e 'target = "/home/hiroaki/public_html/log"; Dir::foreach(target) {|f| if File.file?(target + "/" + f) then p "file: " + f end }'
ruby 1.8.1 (2003-12-25) [i386-cygwin]
"file: .htaccess"
"file: 2002-08-30.html"
"file: 2002-08.html"
"file: 2002-09-02.html"
"file: 2002-09-03.html"
"file: 2002-09-04.html"
"file: 2002-09-10.html"
"file: 2002-09-11.html"
Dir::foreach のサンプルコードを載せてるサイトもいくつか見てみたんだけど、みんな Dir::foreach('.') と書いていたので気づくのが遅れた。
ruby でワンライナーを書くとセミコロンを忘れてエラーになっちゃうことが多いな。perl だったら普段からセミコロンをつけるから忘れないんだけど。
* api.my.yahoo.com に weblogUpdates.ping すると Wrong content-type エラー
昨日 2004-02-20 に、api.my.yahoo.com へ更新通知する方法を書いたが、ruby 1.8.1 の XMLRPC 1.2で weblogUpdates.ping を送るとエラーになってしまった。
何が悪いのかを調べるため、ソースを追いかけてみることにした。/usr/local/lib/ruby/1.8/xmlrpc/client.rb を開く。攻殻機動隊の素子風に言うと「ソースにダイブする」かな。
do_rpc メソッド。538行目付近に例外の発生源があった。
追記。その後、例外を rescue してエラーメッセージを標準エラー出力に出力するようにした。これならスクリプトの実行は継続され、weblogUpdates.ping はとりあえず成功するし。というわけでスクリプトは以下のようになった。
$ script/update_ping.rb http://api.my.yahoo.com/RPC2update_ping.rb は 2004-02-15 に作ったスクリプト。
/usr/local/lib/ruby/1.8/xmlrpc/client.rb:543:in `do_rpc': Wrong content-type (RuntimeError)
from /usr/local/lib/ruby/1.8/xmlrpc/client.rb:409:in `call2'
from /usr/local/lib/ruby/1.8/xmlrpc/client.rb:399:in `call'
from script/update_ping.rb:18
何が悪いのかを調べるため、ソースを追いかけてみることにした。/usr/local/lib/ruby/1.8/xmlrpc/client.rb を開く。攻殻機動隊の素子風に言うと「ソースにダイブする」かな。
do_rpc メソッド。538行目付近に例外の発生源があった。
ct = parse_content_type(resp["Content-Type"]).firstresp というのはレスポンスだな。Content-Type レスポンスヘッダの値が text/xml じゃないと例外を発生させてるわけだ。api.my.yahoo.com はどんな Content-Type を返してきてるんだろう? ct を puts で出力させてみる。
if ct != "text/xml"
if ct == "text/html"
raise "Wrong content-type: \n#{data}"
else
raise "Wrong content-type"
end
end
text/plaintext/plain か。http://api.my.yahoo.com/rss/ping?u=http://sonic64.com/ を叩いたときと同じだな。
- どう修正しよう?
これって api.my.yahoo.com が悪いのかな。それとも、ruby の XMLRPC が厳格過ぎるのかな。この Content-Type をチェックしている if 文を無効にしてしまえばエラーを回避することはできるが、別のところで破綻しそうな気がする。とりあえず api.my.yahoo.com への更新通知は http://api.my.yahoo.com/rss/ping?u=http://sonic64.com/ に HTTP GET する方法を取ることにしよう。 代替手段もあることだし、XMLRPC のレスポンスは xml じゃないとダメなのか、などの深追いはしないことにする。追記。その後、例外を rescue してエラーメッセージを標準エラー出力に出力するようにした。これならスクリプトの実行は継続され、weblogUpdates.ping はとりあえず成功するし。というわけでスクリプトは以下のようになった。
#!/usr/bin/env ruby
require 'xmlrpc/client'
require 'uri'
name = "Landscape - エンジニアのメモ"
url = "http://sonic64.com/"
ping_uri = ARGV.shift
uri = URI.parse(ping_uri)
connection = XMLRPC::Client.new(uri.host, uri.path, uri.port)
result = nil
begin
result = connection.call("weblogUpdates.ping", name, url)
puts "message: " + uri.host + ": " + result["message"]
rescue Exception => e
puts Time::now.to_s + ' error: ' + ping_uri + ": " + e.message.split("\n")[0];
puts Time::now.to_s + ' ' + ping_uri + ': weblogUpdates.ping: error'
exit 1
end
- 余談
今回の記事のタイトルはじつに長くて読みづらい。日本語が7文字しかない。これ以上削れないと思って、「RSS: Ruby: api.my.yahoo.com に weblogUpdates.ping すると Wrong content-type エラー」という長いタイトルにしたんだけど、今考えると Wrong content-type は削ろうと思えば削れるかなあ。「RSS: Ruby: api.my.yahoo.com に weblogUpdates.ping するとエラー」だけでも良かったかも。RSS やサイトの更新を通知する、weblogUpdates.ping を送ってみることにする。
でも、送り方がわからない。MovableType などであれば簡単に送れるらしいが、使ってない場合はどうしたら良いんだろう? そもそも、どこに ping したら良いかもわからない。そのうえ、この記事を書いてる今現在、何で weblogUpdates.ping を送りたくなったかも忘れてしまった。送りたいと思ったのが今朝。遊びに行って、帰ってきてさあやるぞと取りかかった夜頃にはすべて忘れてしまっている。なんかやる気のない日曜の夜。
まずは Google で weblogUpdates.ping を検索。全体のヒット数は少なめだが、名の売れたサイトがいくつかヒットした。
http://hail2u.net/blog/blog/weblogupdates_ping_servers.html
とりあえず紹介されてた全部のサイトに送ってみることにする。あ、Bullkfeeds は 2003-12-23 に書いた HTTP GET で更新を通知する方法を運用済みなので今回は除外。Myblog japan もユーザ登録が大変そうなので除外。今回送るのは以下の7つ。
Weblogs.Com http://rpc.weblogs.com/RPC2
blo.gs http://ping.blo.gs/
BlogRolling http://rpc.blogrolling.com/pinger/
Technorati http://rpc.technorati.com/rpc/ping
ping.bloggers.jp http://ping.bloggers.jp/rpc/
ココログ http://ping.cocolog-nifty.com/xmlrpc
BlogPeople http://www.blogpeople.net/servlet/weblogUpdates
どこに送るかはこれで決まった。あとは、どうやって送るかだ。
Weblogs.Com Ping の Perl による実装
http://naoya.dyndns.org/~naoya/mt/archives/000423.html
やったー、コードを書く手間が省けたーと喜んだのもつかの間。module インストール時に以下のエラーが出てうまくいかない。長いけどエラーメッセージなので書いておこう。
Frontier::Client をインストールしようとしたら XML-Parser に依存していたのでまずは XML-Parser をインストールしようとしたら lexpat というライブラリが無くてビルドができないとのこと。lexpat は SourceForge からダウンロードしてインストールしなければならないらしい。あーもう大変だなあ。perl 以外の方法はないかなあと再び Google で weblogUpdates.ping を検索した結果を見ると、Ruby での実装例があった。やったー。
weblogUpdate の Ruby 実装 : スタンドアローンサーバ版
http://dontstopmusic.no-ip.org/diary/20030911.html#p04
Ruby なら 1.8.1 をインストール済みだし、たいていのライブラリが標準でインストールされるので手間が省けるはず。サンプルを動かしたら問題なく動作したので、ちょっと修正して以下のようにした。もらってきたコードほとんどそのままだけど。
ping 送信先だけは引数で指定できるようにした。本当はウェブサイト名や url も引数で指定できるようにしようと思ったのだが、cygwin のシェルから2バイト文字列を送るとトラブルの元になりそうだったのでハードコーディングしてしまった。また、ruby スクリプト自体も念のため UTF-8 改行コード LF で保存した。UTF-8 はもしかしたら逆にトラブルの元になるかもしれないが、動かしたら問題なかったのでそのまま使うことにした。
あとはこれを
追記。
2004-02-21 に「api.my.yahoo.com に weblogUpdates.ping すると Wrong content-type エラー」という記事を書いた。
でも、送り方がわからない。MovableType などであれば簡単に送れるらしいが、使ってない場合はどうしたら良いんだろう? そもそも、どこに ping したら良いかもわからない。そのうえ、この記事を書いてる今現在、何で weblogUpdates.ping を送りたくなったかも忘れてしまった。送りたいと思ったのが今朝。遊びに行って、帰ってきてさあやるぞと取りかかった夜頃にはすべて忘れてしまっている。なんかやる気のない日曜の夜。
まずは Google で weblogUpdates.ping を検索。全体のヒット数は少なめだが、名の売れたサイトがいくつかヒットした。
- どこに weblogUpdates.ping を送るか
hail2u.net - Weblog - weblogUpdates pingを受け付けているサーバーhttp://hail2u.net/blog/blog/weblogupdates_ping_servers.html
とりあえず紹介されてた全部のサイトに送ってみることにする。あ、Bullkfeeds は 2003-12-23 に書いた HTTP GET で更新を通知する方法を運用済みなので今回は除外。Myblog japan もユーザ登録が大変そうなので除外。今回送るのは以下の7つ。
Weblogs.Com http://rpc.weblogs.com/RPC2
blo.gs http://ping.blo.gs/
BlogRolling http://rpc.blogrolling.com/pinger/
Technorati http://rpc.technorati.com/rpc/ping
ping.bloggers.jp http://ping.bloggers.jp/rpc/
ココログ http://ping.cocolog-nifty.com/xmlrpc
BlogPeople http://www.blogpeople.net/servlet/weblogUpdates
どこに送るかはこれで決まった。あとは、どうやって送るかだ。
- どうやって送るか
先ほどの Google で weblogUpdates.ping を検索した結果を眺めていると、Perl で ping を送るクライアントの実装例があった。Weblogs.Com Ping の Perl による実装
http://naoya.dyndns.org/~naoya/mt/archives/000423.html
やったー、コードを書く手間が省けたーと喜んだのもつかの間。module インストール時に以下のエラーが出てうまくいかない。長いけどエラーメッセージなので書いておこう。
CPAN.pm: Going to build M/MS/MSERGEANT/XML-Parser-2.34.tar.gz
Note (probably harmless): No library found for -lexpat
Expat must be installed prior to building XML::Parser and I can't find
it in the standard library directories. You can download expat from:
http://sourceforge.net/projects/expat/
If expat is installed, but in a non-standard directory, then use the
following options to Makefile.PL:
EXPATLIBPATH=... To set the directory in which to find libexpat
EXPATINCPATH=... To set the directory in which to find expat.h
For example:
perl Makefile.PL EXPATLIBPATH=/home/me/lib EXPATINCPATH=/home/me/include
Note that if you build against a shareable library in a non-standard location
you may (on some platforms) also have to set your LD_LIBRARY_PATH environment
variable at run time for perl to find the library.
Running make test
Make had some problems, maybe interrupted? Won't test
Running make install
Make had some problems, maybe interrupted? Won't install
Running make for K/KM/KMACLEOD/Frontier-RPC-0.06.tar.gz
Is already unwrapped into directory /home/aqua/.cpan/build/Frontier-RPC-0.06
CPAN.pm: Going to build K/KM/KMACLEOD/Frontier-RPC-0.06.tar.gz
cp lib/Frontier/Daemon.pm blib/lib/Frontier/Daemon.pm
cp lib/Frontier/Client.pm blib/lib/Frontier/Client.pm
cp lib/Frontier/RPC2.pm blib/lib/Frontier/RPC2.pm
/usr/bin/make -- OK
Running make test
/usr/bin/perl.exe "-Iblib/lib" "-Iblib/arch" test.pl
1..1
Can't locate XML/Parser.pm in @INC (@INC contains: blib/lib blib/arch /usr/lib/perl5/5.8.2/cygwin-thread-multi-64int /usr/lib/perl5/5.8.2 /usr/lib/perl5/site_perl/5.8.2/cygwin-thread-multi-64int /usr/lib/perl5/site_perl/5.8.2 /usr/lib/perl5/site_perl .) at blib/lib/Frontier/RPC2.pm line 14.
BEGIN failed--compilation aborted at blib/lib/Frontier/RPC2.pm line 14.
Compilation failed in require at blib/lib/Frontier/Client.pm line 14.
BEGIN failed--compilation aborted at blib/lib/Frontier/Client.pm line 14.
Compilation failed in require at test.pl line 19.
BEGIN failed--compilation aborted at test.pl line 19.
not ok 1
make: *** [test_dynamic] Error 2
/usr/bin/make test -- NOT OK
Running make install
make test had returned bad status, won't install without force
Frontier::Client をインストールしようとしたら XML-Parser に依存していたのでまずは XML-Parser をインストールしようとしたら lexpat というライブラリが無くてビルドができないとのこと。lexpat は SourceForge からダウンロードしてインストールしなければならないらしい。あーもう大変だなあ。perl 以外の方法はないかなあと再び Google で weblogUpdates.ping を検索した結果を見ると、Ruby での実装例があった。やったー。
weblogUpdate の Ruby 実装 : スタンドアローンサーバ版
http://dontstopmusic.no-ip.org/diary/20030911.html#p04
Ruby なら 1.8.1 をインストール済みだし、たいていのライブラリが標準でインストールされるので手間が省けるはず。サンプルを動かしたら問題なく動作したので、ちょっと修正して以下のようにした。もらってきたコードほとんどそのままだけど。
- weblogUpdates.ping を送る Ruby スクリプト
Ruby 1.8.1 + cygwin で動作を確認。#!/usr/bin/env ruby
require 'xmlrpc/client'
require 'uri'
name = "Landscape - エンジニアのメモ"
url = "http://sonic64.com/"
ping_uri = ARGV.shift
uri = URI.parse(ping_uri)
connection = XMLRPC::Client.new(uri.host, uri.path, uri.port)
result = connection.call("weblogUpdates.ping", name, url)
puts "message: " + uri.host + ": " + result["message"]
ping 送信先だけは引数で指定できるようにした。本当はウェブサイト名や url も引数で指定できるようにしようと思ったのだが、cygwin のシェルから2バイト文字列を送るとトラブルの元になりそうだったのでハードコーディングしてしまった。また、ruby スクリプト自体も念のため UTF-8 改行コード LF で保存した。UTF-8 はもしかしたら逆にトラブルの元になるかもしれないが、動かしたら問題なかったのでそのまま使うことにした。
あとはこれを
$ ~/script/update_ping.rb http://rpc.weblogs.com/RPC2などとして ping するだけだ。
追記。
2004-02-21 に「api.my.yahoo.com に weblogUpdates.ping すると Wrong content-type エラー」という記事を書いた。
2004-02-03 の 「peeraddr が遅い?」の続き。もはや Ruby とは関係ないんだろうけど、続きということで。
[ruby-list:24947] Re: win32 版でのTCPServer#addr, peeraddr の挙動
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/ ...
速くなったのはうれしいけど、NetBIOS over TCP/IP を無効にするとファイル共有できなくなっちゃうんじゃなかったっけ? LAN でファイル共有できなくなったら困るし、WINS や NetBIOS って関わりたくないんだけど一応調べておくか。Google で NetBIOS over TCP/IP 無効を検索。うーん、やっぱり無効にするとファイル共有はできなくなっちゃうみたい。ブラウズマスタを立てれば良いのかもしれないけど、とりあえず今はこのままでも良いか。ファイル共有のことはその時考えるとしよう。
[ruby-list:24947] Re: win32 版でのTCPServer#addr, peeraddr の挙動
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/ ...
後者は,WINSかNetBIOSがらみの問題と推測されます.なるほど・・・。では、NetBIOS を無効にしてみよう。
たとえば,以下が考えられます.
o WINSサーバーのアドレスが間違っているかサーバーが落ちている
o まともなブラウズマスタがいない
"nbtstat -A 存在しないIPアドレス"を実行したときのタイムアウトが
約5秒なので,5秒×2 = 10秒というのは納得できます.
- NetBIOS over TCP/IP を無効にしたら速くなった。
$ time script/mftp.rb -w
["AF_INET", 80, "216.239.57.104", "216.239.57.104"]
real 0m0.564s
user 0m0.160s
sys 0m0.150s
速くなったのはうれしいけど、NetBIOS over TCP/IP を無効にするとファイル共有できなくなっちゃうんじゃなかったっけ? LAN でファイル共有できなくなったら困るし、WINS や NetBIOS って関わりたくないんだけど一応調べておくか。Google で NetBIOS over TCP/IP 無効を検索。うーん、やっぱり無効にするとファイル共有はできなくなっちゃうみたい。ブラウズマスタを立てれば良いのかもしれないけど、とりあえず今はこのままでも良いか。ファイル共有のことはその時考えるとしよう。
Ruby で net/ftp.rb を使った ftp アップローダを書いてみたが、ファイルの転送が始まるまでが異常に遅い。CPU を食いつぶしてるわけでも、ディスクが遅いわけでも、帯域が足りないわけでもない。どうも、コネクションの生成あたりで時間がかかっているようなのだ。
チェックの結果、net/ftp の putbinaryfile メソッドの呼び出しに時間がかかっていることがわかった。さらに以下のようにして net/ftp.rb のメソッドを追跡していったところ af = (@sock.peeraddr)[0] が遅いことがわかった。
[ruby-list:24928] Re: win32 版でのTCPServer#addr, peeraddr の挙動
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/ ...
うーん、WINS なんて使ってないし・・・。ipconfig で確かめてみるか。
・・・? あれ? Primary WINS Server に 192.168.0.200 が指定されてるぞ? これが原因? うーん、プロバイダに問い合わせてみるか・・・。でも、perl だと問題なくて Ruby だと問題あるんだよなあ。私の設定が悪いのかなあ。現象を再現できる最小の ruby のコードと、他の言語で類似のコードを書いて、原因を絞り込んでみよう。
ちなみに、
他の言語でも遅いのかを調べるために、perl でテストコードを書いてみた。
やっぱり一瞬で終わる。
続きは明日 2004-02-04 にやろう。
- 環境
OS は Windows2000 SP4。あとは以下の通り。$ uname -a
CYGWIN_NT-5.0 s 1.5.5(0.94/3/2) (2003-09-20) 16:31 i686 unknown unknown Cygwin
$ ruby -v
ruby 1.8.1 (2003-12-25) [i386-cygwin]
- net/ftp.rb を追跡
printf("%s: %s: %d\n", Time.now - start, __FILE__, __LINE__) をftp アップロードスクリプトの至る所に仕掛け、表示される時間をチェックしていく。start = Time.now
printf("%s: %s: %d\n", Time.now - start, __FILE__, __LINE__)
何らかの処理
printf("%s: %s: %d\n", Time.now - start, __FILE__, __LINE__)
チェックの結果、net/ftp の putbinaryfile メソッドの呼び出しに時間がかかっていることがわかった。さらに以下のようにして net/ftp.rb のメソッドを追跡していったところ af = (@sock.peeraddr)[0] が遅いことがわかった。
def sendport(host, port)
start = Time.now
printf("%s: %s: %d\n", Time.now - start, __FILE__, __LINE__)
af = (@sock.peeraddr)[0]
printf("%s: %s: %d\n", Time.now - start, __FILE__, __LINE__)
- Google に聞いてみる
Google で ruby peeraddr 遅い を検索するとヒット。[ruby-list:24928] Re: win32 版でのTCPServer#addr, peeraddr の挙動
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/ ...
その,i386-mswin32版のRuby 1.4.5を落としてWindows 98で使って
みましたが,まったく問題ありませんでした.
単に名前解決に時間がかかっているだけだと思います.
つまり,ネットワークの環境と設定の問題と考えられます.
telnetで接続した直後にnetstat -aを実行してみて出力がひっかからずに
スムーズに表示されますか?
どこかでひっかかるようなら,表示が遅い行のIPアドレスの逆引きが
うまくいっていないということなので,DNSかWINSかhostsの設定を
確認してみてください.
うーん、WINS なんて使ってないし・・・。ipconfig で確かめてみるか。
$ ipconfig /all
Windows 2000 IP Configuration
Host Name . . . . . . . . . . . . : s
Primary DNS Suffix . . . . . . . :
Node Type . . . . . . . . . . . . : Hybrid
IP Routing Enabled. . . . . . . . : No
WINS Proxy Enabled. . . . . . . . : No
DNS Suffix Search List. . . . . . : ***.**.jp
Ethernet adapter ローカル エリア接続 2:
Connection-specific DNS Suffix . : ***.**.jp
Description . . . . . . . . . . . : 3Com EtherLink XL 10/100 PCI TX NIC (3C905B-TX)
Physical Address. . . . . . . . . : 00-**-**-**-**-**
DHCP Enabled. . . . . . . . . . . : Yes
Autoconfiguration Enabled . . . . : Yes
IP Address. . . . . . . . . . . . : 192.168.0.2
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.0.1
DHCP Server . . . . . . . . . . . : 192.168.0.1
DNS Servers . . . . . . . . . . . : ***.**.1.3
Primary WINS Server . . . . . . . : 192.168.0.200
Lease Obtained. . . . . . . . . . : 2004年2月3日 1:00:13
Lease Expires . . . . . . . . . . : 2004年2月4日 1:00:13
・・・? あれ? Primary WINS Server に 192.168.0.200 が指定されてるぞ? これが原因? うーん、プロバイダに問い合わせてみるか・・・。でも、perl だと問題なくて Ruby だと問題あるんだよなあ。私の設定が悪いのかなあ。現象を再現できる最小の ruby のコードと、他の言語で類似のコードを書いて、原因を絞り込んでみよう。
- ruby でのテストコード
#!/usr/local/bin/ruby -l上記のコードを実行すると、以下のようになる。なんで5秒もかかるんだろう?
require 'socket'
sock = TCPSocket.open('www.google.co.jp', '80')
p sock.peeraddr
$ time script/mftp.rb -w
["AF_INET", 80, "216.239.57.104", "216.239.57.104"]
real 0m5.535s
user 0m0.200s
sys 0m0.130s
ちなみに、
sock = TCPSocket.open('216.239.57.104', '80')と IP アドレスで指定しても5秒かかる。OS 付属の nslookup だとなぜか一瞬で終わる。
$ time nslookup www.google.co.jp
Server: dns1.****.**.jp
Address: ***.**.1.3
Non-authoritative answer:
Name: www.google.akadns.net
Addresses: 216.239.57.104, 216.239.57.99
Aliases: www.google.co.jp, www.google.com
real 0m0.545s
user 0m0.010s
sys 0m0.030s
他の言語でも遅いのかを調べるために、perl でテストコードを書いてみた。
#!/usr/bin/perl
use Socket;
my ($host, $port, $ip, $sockaddr, $buf);
$host = 'www.google.co.jp';
$port = 80;
# ソケットの生成
$ip = inet_aton($host) || die "host($host) not found.\n";
$sockaddr = pack_sockaddr_in($port, $ip);
socket(SOCKET, PF_INET, SOCK_STREAM, 0) || die "socket error.\n";
# ソケットの接続
connect(SOCKET, $sockaddr) || die "connect $host $port error.\n";
# 終了処理
close(SOCKET);
やっぱり一瞬で終わる。
$ time script/perlsock.pl
real 0m0.992s
user 0m0.240s
sys 0m0.180s
続きは明日 2004-02-04 にやろう。
今まで ftp でファイルをアップロードするとき、簡単なシェルスクリプトを使っていた。しかし、回線の品質が悪かったりすると、タイムアウトが発生したりすることがある。ファイル数が少ないうちはタイムアウトを待つ時間は大したことはないのだが、数百や数千ファイルを扱うときはタイムアウト待ちの時間は無視できなくなる。シングルスレッドではなく、マルチスレッドで動作させれば待ち時間を少なくすることができる。
シェルスクリプトでもコマンド末尾に & を付けてバックグラウンドで実行すればサーバへの接続を複数持つことはできるが、それでは面白くない。せっかくだから使ったことのない言語で書きたい。以前から Ruby を学びたいと考えていたので、Ruby でコードを書くことにした。Ruby ではスレッドを簡単に扱えるようになっているので、今回の課題にはうってつけだろう。
チュートリアル: Thread - オブジェクト指向言語Ruby
http://www.ruby-lang.org/ja/20020315.html
逆引きRuby - スレッド
http://www.namaraii.com/rubytips/?%A5%B9%A5%EC%A5%C3%A5%C9#l ...
Ruby 1.8.1 には ftp 用のクラスが標準添付されており、簡単に書くことはできた。ただし正常に動作しない。もちろん、Ruby が悪いのではなく、私のコードが、とくにスレッドの使い方が悪い。
実行するとものすごい勢いでスレッドが生成されていき、それと同時に ftp コネクションも作られていく。ファイルが100個あると、スレッドも100個、ftp コネクションも100個というすさまじいクライアントになってしまった。こんなクライアントでは大富豪が運営するサーバにしか接続できないだろう。とりあえず生成するコネクションの上限を設け、コネクションプールを実装しようとしたが、今度は転送が終わって再利用可能になったはずのコネクションを再利用できなかったりと、問題がたくさんある。どうやら私はまったくスレッドを使いこなせていないようだ。今までシングルスレッドのコードしか書いたことがなかったが、マルチスレッドの概念くらいは理解しているつもりだった。排他制御が要だとか、DB のロックと同じレベルだと思ってたが、甘かったようだ。
これはいい本だ。私は Java は情報処理の試験で使うために文法だけ覚えただけだが、十分わかる。そもそもスレッドとは何か、というところから始まっているので、Java を知らなくても読んでいける。サポートサイトもあるのでメモしておこう。
『Java言語で学ぶデザインパターン入門 マルチスレッド編』
http://www.hyuki.com/dp/dp2.html
シェルスクリプトでもコマンド末尾に & を付けてバックグラウンドで実行すればサーバへの接続を複数持つことはできるが、それでは面白くない。せっかくだから使ったことのない言語で書きたい。以前から Ruby を学びたいと考えていたので、Ruby でコードを書くことにした。Ruby ではスレッドを簡単に扱えるようになっているので、今回の課題にはうってつけだろう。
- 大富豪的プログラミング
以下のサイトを参考に、スレッドを使った ftp クライアントを書いてみた。チュートリアル: Thread - オブジェクト指向言語Ruby
http://www.ruby-lang.org/ja/20020315.html
逆引きRuby - スレッド
http://www.namaraii.com/rubytips/?%A5%B9%A5%EC%A5%C3%A5%C9#l ...
Ruby 1.8.1 には ftp 用のクラスが標準添付されており、簡単に書くことはできた。ただし正常に動作しない。もちろん、Ruby が悪いのではなく、私のコードが、とくにスレッドの使い方が悪い。
実行するとものすごい勢いでスレッドが生成されていき、それと同時に ftp コネクションも作られていく。ファイルが100個あると、スレッドも100個、ftp コネクションも100個というすさまじいクライアントになってしまった。こんなクライアントでは大富豪が運営するサーバにしか接続できないだろう。とりあえず生成するコネクションの上限を設け、コネクションプールを実装しようとしたが、今度は転送が終わって再利用可能になったはずのコネクションを再利用できなかったりと、問題がたくさんある。どうやら私はまったくスレッドを使いこなせていないようだ。今までシングルスレッドのコードしか書いたことがなかったが、マルチスレッドの概念くらいは理解しているつもりだった。排他制御が要だとか、DB のロックと同じレベルだと思ってたが、甘かったようだ。
- 先人の知恵に頼れ
こうなったらもう本を読んだ方がいい。ちょうど後輩が結城さんのデザインパターン本のマルチスレッド編を持っていたので、借りてきた。これはいい本だ。私は Java は情報処理の試験で使うために文法だけ覚えただけだが、十分わかる。そもそもスレッドとは何か、というところから始まっているので、Java を知らなくても読んでいける。サポートサイトもあるのでメモしておこう。
『Java言語で学ぶデザインパターン入門 マルチスレッド編』
http://www.hyuki.com/dp/dp2.html
cygwin 環境に ruby 1.8.1 をインストールする。Google で cygwin ruby で検索するとヒット。
Yet another Ruby porter, - RubyをCygwin環境で作る
http://wiki.fdiary.net/yarp/?Ruby%A4%F2Cygwin%B4%C4%B6%AD%A4 ...
なんだ、cygwin だからいろいろ設定を変えてコンパイルしなければならないのかと思ってたけど、そうでもないんだね。ただ、今回入れようとしてるのは1.8.1だからそこで何かはまるかもしれない。
ruby オフィシャルサイトから ruby 1.8.1 の tar ボールをダウンロード。あとは、上記コマンドをそのままコピ & ペースト。友達に初詣に誘われたので出かけてしまったので、make にどれくらい時間がかかったかはわからない。やっと新年らしい言葉が出てきたな。
さて、初詣から帰ってくると make check に失敗してる。
cygwin なのでパーミッションをいい加減な設定にしてたために Insecure world writable dir /home/aqua, mode 040777 という警告が出てるが、マルチユーザの unix システムを使ってる訳じゃないから気にしないでおこう。とりあえずテストは成功。make install して完了。さっきの wiki に 1.8.1 だと make test だよと加筆しておこう。
Yet another Ruby porter, - RubyをCygwin環境で作る
http://wiki.fdiary.net/yarp/?Ruby%A4%F2Cygwin%B4%C4%B6%AD%A4 ...
Cygwin版Rubyのビルドとインストール
単に
% tar xfvz ruby-1.6.8.tar.gz
% cd ruby-1.6.8
% mkdir BUILD_cygwin; cd BUILD_cygwin
% ../configure --enable-shared && make && make check
% make install
するだけ。
なんだ、cygwin だからいろいろ設定を変えてコンパイルしなければならないのかと思ってたけど、そうでもないんだね。ただ、今回入れようとしてるのは1.8.1だからそこで何かはまるかもしれない。
ruby オフィシャルサイトから ruby 1.8.1 の tar ボールをダウンロード。あとは、上記コマンドをそのままコピ & ペースト。友達に初詣に誘われたので出かけてしまったので、make にどれくらい時間がかかったかはわからない。やっと新年らしい言葉が出てきたな。
さて、初詣から帰ってくると make check に失敗してる。
make: *** No rule to make target `check'. Stop.make test ならどうだ。
$ make test
../rubytest.rb:37: warning: Insecure world writable dir /home/aqua, mode 040777
/home/aqua/ruby-1.8.1/sample/test.rb:1643: warning: Insecure world writable dir /home/aqua, mode 040777
/home/aqua/ruby-1.8.1/sample/test.rb:1650: warning: Insecure world writable dir /home/aqua, mode 040777
/home/aqua/ruby-1.8.1/sample/test.rb:1651: warning: Insecure world writable dir /home/aqua, mode 040777
/home/aqua/ruby-1.8.1/sample/test.rb:1658: warning: Insecure world writable dir /home/aqua, mode 040777
/home/aqua/ruby-1.8.1/sample/test.rb:1668: warning: Insecure world writable dir /home/aqua, mode 040777
/home/aqua/ruby-1.8.1/sample/test.rb:1669: warning: Insecure world writable dir /home/aqua, mode 040777
test succeeded
cygwin なのでパーミッションをいい加減な設定にしてたために Insecure world writable dir /home/aqua, mode 040777 という警告が出てるが、マルチユーザの unix システムを使ってる訳じゃないから気にしないでおこう。とりあえずテストは成功。make install して完了。さっきの wiki に 1.8.1 だと make test だよと加筆しておこう。
SoftwareDesign で高林さんの pdumpfs の記事を読んだら、
なんだか ruby をインストールしたくなった。
たしか先日 1.80 がリリースされたはず。ここは一発チャレンジだ。
まだ枯れてはいない 1.80 をインストールする。
コンパイルとインストールはとくにトラブル無く進んだ。
ただ、make test の結果が、
の一行だけってのがなんか寂しい気もした。
何のテストしてるのかもよくわからないし。まあいいか。
ということで、インストール完了。
なんだか ruby をインストールしたくなった。
たしか先日 1.80 がリリースされたはず。ここは一発チャレンジだ。
まだ枯れてはいない 1.80 をインストールする。
wget http://www.t.ring.gr.jp/archives/lang/ruby/1.8/ruby-1.8.0.tar.gz
tar zxvf ruby-1.8.0.tar.gz
cd ruby-1.8.0/
./configure && make && make test
su
make install
コンパイルとインストールはとくにトラブル無く進んだ。
ただ、make test の結果が、
test succeeded
の一行だけってのがなんか寂しい気もした。
何のテストしてるのかもよくわからないし。まあいいか。
ということで、インストール完了。