Hatena::ブログ(Diary)

bonlife このページをアンテナに追加 RSSフィード

言及ISBN/ASIN
  • Ringo EXPO 08 [DVD]
  • 三文ゴシップ
  • my way
  • ビジネスパーソンのための話し方入門 (日経文庫)
  • ザ・グーグルウェイ グーグルを成功へ導いた型破りな戦略
  • 考え・書き・話す3つの魔法
  • 自分の答えのつくりかた―INDEPENDENT MIND

2008-07-12 夏過ぎて困る。

「VOA Special EnglishのPodcast用RSS生成 for Ruby」を勝手に添削 (できるほどRubyistではないのに)

うっかりTOEICを申し込んでしまったので、ちょっと本気で英語勉強しよ!と思っているbonlifeです。英語に耳を慣らすためにPodcastの購読を増やすことを検討VOAあたりが良いってウワサを聞いたことがあったので調べてみると、何やら便利そうなものを公開してる人発見です!

ほうほう、と思ってリンク先のリンク先のRSSiTunesに登録すると、descriptionが出てくるヤツと出てこないヤツがあってガッカリ…。仕方なくあんまり得意じゃないRubyコードを見てみると、以下のような箇所が…。

	body = if xhtml =~ /<span class="body">(.+?)<\/span>/mi then $1 else "" end

うわぁん、なぜか最短一致使ってる!これだと関係ないspanタグが途中にあると、そこで終わってしまう罠。

思い立ったが吉日生活、ということで頑張ってRubyコード書き直してみました。Rubyコードとして根本的に色々間違ってそうな予感です。 (「Hpricot で link タグが勝手に空要素になる - kaeruspoon」の現象でハマったのは秘密です。)

  • VOARSSGenerator.rb
#!/usr/bin/ruby
# VOA Special English Poscast Generator

require 'cgi'
require 'date'
require 'hpricot'
require 'open-uri'

class VOARSSGenerator

  @@TARGET_URL = "http://www.voanews.com/specialenglish/customCF/RecentStoriesRSS.cfm?keyword=TopStories"

  def generate_voa_rss
    generate_rss_header
    xml = Hpricot.XML(open(@@TARGET_URL))
    (xml/"rss/channel/item").each do |item|
      title = (item/"title").text
      link  = (item/"link").text
      mp3, wordcount, date, description = parse_each_link(link)
      generate_item_xml(title, link, mp3, wordcount, date, description)
    end
    generate_rss_footer
  end

  def generate_rss_header
    puts <<HEAD
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>VOA Special English</title>
<link>http://www.voanews.com/specialenglish/</link>
<description>VOA Special English</description>
<language>en</language>
<pubDate>#{DateTime.now.strftime('%a, %d %b %Y %H:%M:%S %z')}</pubDate>
HEAD
  end

  def generate_rss_footer
    puts <<FOOT
</channel>
</rss>
FOOT
  end

  def parse_each_link(link)
    doc = Hpricot(open(link))
    ((doc/"span.body/")./".imagecaption").remove
    description = (doc/"span.body").first.inner_html.gsub(/\n+/," ").gsub(/<br(?: ?\/)?>/,"\n").gsub(/<\/p>/,"\n\n").gsub(/&nbsp;/," ").gsub(/<\/?[^>]*>/, " ").gsub(/ +/," ").gsub(/^ /,"").sub(/^\s+/,"").sub(/\s+\Z/m,"")
    date = Date.parse((doc/"span.datetime/em").first.inner_html)
    mp3 = ((doc/"a.media-asset").first)[:href]
    wordcount = description.scan(/\w+/).length
    return mp3, wordcount, date, description
  end

  def h(st)
    CGI::escapeHTML(st)
  end

  def generate_item_xml(title, link, mp3, wordcount, date, description)
    puts <<ITEM
<item>
<title>[#{wordcount}] #{h title}</title>
<link>#{h link}</link>
<pubDate>#{date.strftime('%a, %d %b %Y %H:%M:%S %z')}</pubDate>
<dc:creator>VOA</dc:creator>
<dc:subject>English</dc:subject><dc:subject>News</dc:subject>
<description><![CDATA[
#{description}
]]></description>
<enclosure url="#{h mp3}" length="" type="audio/mpeg" />
</item>
ITEM
  end

end

v = VOARSSGenerator.new
v.generate_voa_rss

parse_each_link の description まわりの処理がおそろしく力技。しかも微妙。関数に値を色々と渡してるところもダサいけど、まぁ良いか。後、CDATAに入れるデータがどうなってれば良いのか、とかXMLの基本が分かってないんですよね。(そのあたりはまた後で勉強しよっと。) id:n_shuyoさんが部分的に修正してくれると嬉しいなぁ、とかなんとか。

2007-06-27 今日はUNIQLOのYシャツ着てます。

Oracleで件数の多いテーブルを日付をキーに1ヶ月分ずつ区切って検索するサンプル

紀伊國屋書店で「Python クックブック 第2版」を買ったbonlifeです。O'reillyのWeb直販で買えば、キャンペーンで『Pythonクックブック』Tシャツもらえたのに。

さてさて、MViewとかサマリテーブルとか作っておけば良かった…orz ってぐらいの集計処理を後からしなきゃいけなくなった時のためのメモです。

データ件数が多いテーブルの場合、適当な件数に絞って処理をしないとそりゃあもう大変なことになります。そんな時は、DATE型のフィールドをWHERE句の検索条件に追加し、データ件数に応じて1日分とか1週間分、1ヶ月分の情報だけを対象に処理させると便利。ただ、このやり方の場合、1ヶ月分ずつ区切ったとしても、1年分を検索するには12回SQLを発行しなきゃいけなくなります。SQLがすぐに返ってくる場合は良いですが、多少無理をさせている場合、「まだかな、まだかなー。」と待つ必要があり、結構なストレス…。そんな時はスクリプト言語の力を借りれば良いじゃないの!ということでRubyを使ってみました。(PL/SQLにチャレンジしてみようと思ったのですが、SELECTにINTOが必要とかなんとか理不尽なことを言われたので、挫折しましたよ、これ。)

あるテーブル(TABLE_A)のデータ件数を日ごとに集計するサンプルです。(1ヶ月分ずつ検索範囲を区切ってます。)

  • ora_count_by_date.rb
#!/usr/bin/env ruby

require 'oci8'
require 'date'

# Oracleに接続

conn = OCI8.new('user','password','localhost')

# ヒアドキュメントを使ってSQLの雛形を準備
# 絞込みのためのDATE型の検索条件には、バインド変数を指定

sql = <<EOS
SELECT   TRUNC(DATE_A,'DD'), COUNT(*)
FROM     TABLE_A
WHERE    DATE_A >= TO_DATE(:from_month,'YYYYMM')
  AND    DATE_A <  TO_DATE(:to_month,'YYYYMM')
GROUP BY TRUNC(DATE_A,'DD')
EOS

# とりあえずparseしてみる

cursor = conn.parse(sql)

# 検索期間をDateで指定

from   = Date.new(2006,4,1)
to     = Date.new(2007,4,1)
target = from

# 検索条件を月単位でインクリメント( >> 1)しながら処理
# バインドする値を毎回変更

while target <= to
  cursor.bind_param(':from_month',target.strftime('%Y%m'))
  cursor.bind_param(':to_month',(target >> 1).strftime('%Y%m'))
  cursor.exec()
  while r = cursor.fetch()
    puts r.join("\t")
  end
  target = target >> 1
end

よくあるサンプルだと、カンマでjoin()してCSVにしてますが、やっぱり"\t"でjoin()してTSVにした方がEXCELに貼り付けやすくて便利な気がします。以下のようにしてファイルリダイレクトして、使ったり。

> ruby ora_count_by_date.rb > output.txt

Python本買っといてアレですが、Rubyも良いですね。

[参考URL]

2007-05-31 スーッとしたいだけなんです。

Exerbを使って単純にファイルを分割するツールを作ってみたよメモ

シャンプーをサクセスに変えてみたbonlifeです。抜け毛が気になってきたわけではありません。

さてさて、会社ではAIX使ってるのですが、そこで使ってる簡易ツールをWindows上でも使えるようにしたいな、と思い、練習がてらRubyで簡単なスクリプトを書いてみました。(ってのはほとんど嘘で、単調な仕事をしていたら退屈なので暇つぶしに別のことをやってみました。) Rubyで手続き型!って感じのスクリプトを書くとちょっぴり罪悪感。Perlだったらなんとなく許されそうな気分なんですけどね。(今回、Rubyを使ってみたのには特に理由はありません。どの言語もいまいち使いこなせていないので…orz)

EDIデータについてる固定長のヘッダ部分を分割するツールです。あえてスクリプトにしなくても良いぐらい簡単なものですが、バイナリデータの場合、Windowsではちょっと扱いづらかったりするので。そういえば、スクリプトをWindows実行形式ファイルに変換するツールがあったよなぁ、と思い出し、Exerbにチャレンジ。ダウンロードして setup.rb を実行したら、準備OKです。

まずは、Rubyのスクリプトを書きます。optparseを初めて使ってみましたが、-h(--help)、-v(--version) に最初から対応していたりして、結構便利っぽいです。あと、Rubyは引数のワイルドカード展開に対応していて、シェルスクリプトっぽく書けて気分が良いです。ということで、出来たスクリプトはこんな感じ。

  • divhdr.rb
#!/usr/bin/env ruby
#
# divhdr (ファイルの先頭部分を切り取るスクリプト)
#

require "optparse"

# オプションの解析

Version = "1.0.0"
length = 156

parser = OptionParser.new
parser.banner = "Usage: divhdr [options] file [file ...]"
parser.on('-l LENGTH') {|v| length = v.to_i}

def parser.usage()
  $stderr.puts help()
  exit(1)
end

begin
  parser.parse!
rescue
  parser.usage()
end

if ARGV.length < 1
  parser.usage()
end

# 読み込み用ファイルオブジェクトの取得

def get_file_obj(file)
  if FileTest.file?(file)
    begin
      return File.open(file,"rb")
    rescue
      $stderr.puts "ERR : Can't open '#{file}'"
    end
  else
    $stderr.puts "ERR : Can't get file object from '#{file}'"
  end
end

# 出力ファイル名生成

def out_filename_generate(file_name)
  return file_name + ".hdr", file_name + ".msg"
end

# ファイル出力

def out_to_file(file_name, contents)
  begin
    File.open(file_name,"wb").write(contents)
  rescue
    $stderr.puts "ERR : Can't write to file '#{file}'"
  end
end

# ファイル分割

def div_contents(file_obj,length)
  hdr = file_obj.read(length)
  msg = file_obj.read()
  return hdr, msg
end

# 引数を読み込んで処理

ARGV.each do |i|
  fo = get_file_obj(i)
  if fo
    hdr, msg = div_contents(fo,length)
    hdr_name, msg_name = out_filename_generate(i)
    out_to_file(hdr_name,hdr)
    out_to_file(msg_name,msg)
  end
end

このファイルをExerbでWindows実行形式ファイルに変換します。

C:\test\ruby> ruby -r exerb/mkexy divhdr.rb
C:\test\ruby> exerb divhdr.exy

これで divhdr.exe が出来ちゃうんだから素敵。なんと言っても簡単!

ちなみに、最初、ちゃんとドキュメントを読まずにRubyスクリプトをいきなりexerbしたら、以下のようなことになっちゃいました。

C:\test\ruby> exerb divhdr.rb
C:\test\ruby> divhdr *
divhdr.rb:6:in `require': No such file to load -- optparse (LoadError)
        from divhdr.rb:6

exe への変換までは出来ますが、実行時に外部モジュールを読み込めずにエラーになりましたよ。外部モジュールを使う場合は、必ず mkexy してレシピファイルを事前に用意しておかないといけないみたいですね。なるほど、なるほど。これを活かして、何かもうちょっと自分の仕事に役に立つことをやりたいなぁ…と思ったり思わなかったりな今日この頃です。

2007-02-06 勉強が間に合いそうにありませんが、負けません。

MD5ハッシュ値を表示するスクリプトサンプル

最近C言語の勉強を始めたbonlifeです。先日、ベンダーから会社で使っているミドルウェアのPatchが送られてきた時、MD5のハッシュ値がメールに記載されていました。今まであまり気にしたことはなかったのですが、せっかくなので、簡単なスクリプトを書いて、確認してみることにしました。得意な言語がないので、色々と試しちゃいましたよ。(ファイルのMD5ハッシュ値を調べることなんて滅多にないとは思いますが…。)

  • Perlのサンプル
#! /usr/local/bin/perl

use strict;
use warnings;

use Digest::MD5 qw(md5 md5_hex);

my @files;

foreach (@ARGV) {
    push @files, glob "$_" ;
}

foreach (@files) {
    eval {
        open (FH, $_) or die "Can't open '$_' : $!";
        binmode(FH);
        print Digest::MD5->new->addfile(*FH)->hexdigest, " : $_\n";
        close(FH);
    };
    if ($@) {
        print "error occured while processing '$_' : $!\n";
    }
}
  • Rubyのサンプル
require 'digest/md5'

ARGV.each {|file|

  begin
    print Digest::MD5.new(File.open(file,'rb').read), " : ", file , "\n"
  rescue
    abort(("error occured while processing '" << file << "' : " << $!))
  end

}
# coding: utf-8
import sys
import glob
import md5

# [参考URL]
# http://white.s151.xrea.com/wiki/index.php?cmd=read&page=memo%2Fpython%2Fmd5

def getMd5(path):
    m = md5.new()
    for f in open(path,'rb'):
        m.update(f)
#    m.update(open(path,'rb').read())
    return m.hexdigest()

for i in range(1,len(sys.argv)):
    for f in glob.glob(sys.argv[i]):
        try:
            print getMd5(f), ":", f
        except:
            print "error occured while processing '" + f + "' :", sys.exc_info()[0]
  • PHPのサンプル
<?php

for ( $i = 1; $i < count($argv) ; $i++ ){
    foreach (glob($argv[$i]) as $file){
        print md5_file($file) . " : $file\n";
    }
}

?>

全て結果は同じになりました!という当たり前の結果にちょっと感動したりして(苦笑)。

PHPをコマンドラインで実行するのってやっぱり違和感ありますね。専用の関数があって一番楽なんですけどね。引数の1番目をどう扱うのかが言語によって違ってたりするのを体感できて面白かったです。Pythonだけ(他の人のスクリプトを参考にして)関数を定義していたり、バランスが良くないですが、まぁ、ご愛嬌。

ところで、そもそもMD5のハッシュ値ってどういう意味があるんでしょう。よく、ファイルをダウンロードできるところで、そのファイルのMD5ハッシュ値が記載されてたりしますが、改ざんに対しては無防備ですよね。(ファイル書き換えられるぐらいなら、WEBページも同時に書き換えるでしょうし。)DL途中にファイルが壊れなかったことの確認にはなるかしら。

最後に-Mスイッチでモジュール呼び出してる無理矢理感のあるPerlワイライナーも載せておきます。

  • UNIXの場合
perl -MDigest::MD5 -e 'foreach (@ARGV) { open (FH, $_) or die "open \"$_\": $! ";binmode(FH);print Digest::MD5->new->addfile(*FH)->hexdigest, " : $_\n";close(FH); }' filename [filename...]
  • Windows(コマンドプロンプト)の場合
perl -MDigest::MD5 -e "foreach (@ARGV) { open (FH, $_) or die \"open \'$_\' : $! \";binmode(FH);print Digest::MD5->new->addfile(*FH)->hexdigest, \" : $_\n\";close(FH); }" filename [filename...]

そんなこんなで、ワンライナーの中でシングルクォートを使う方法が分からないbonlifeなのでした。(誰かご存知でしたら教えてくださいませ。)

2007-02-02 お金の話は苦手です。

3つのテキストファイルを比較するサンプル (Ruby素人編)

VMWare Serverを会社のノートPCにインストールして、CentOS使ってあれこれ実験しようとしたところ、yumが妙なエラーで止まってしまい苦戦しているbonlifeです。FAQの5番目にある"[Errno -1] Header is not complete."ってヤツですよ…。ということで、挫折感に打ちひしがれながらRubyでスクリプトを書いてみました。(かなり久しぶり!)

3つのテキストファイルの内容を1行ずつ比較するスクリプトです。何をしようとしているのかは、以下でご確認くださいませ。

#!/usr/bin/env ruby
#
# by bonlife
#

# arguments check

if ARGV.length != 3 then
  print $0 , " needs 3 files.\n"
  exit(1)
end

# file to array

def make_array(f)
  f.readlines.each{|x|
    x.chomp!
  }
end

# pass arguments to make_array()
# generate array_list

array_list = Array.new

begin
  (0 .. ARGV.length - 1).each {|i|
    array_list << make_array(File.open(ARGV[i]))
  }
rescue
  abort(("error : " << $!))
end

# (for debug)
# p array_list

# display result

array_list[0].each {|pkg|

  line = pkg + ','

  if array_list[1].include?(pkg) then
    line << "included,"
  else
    line << "not included,"
  end

  if array_list[2].include?(pkg) then
    line << "included\n"
  else
    line << "not included\n"
  end

  print line
}

どうでしょう。求む添削です。自分で思ったのは以下の点。

  • ARGVをそこそこ有効活用
  • Range#eachがRubyっぽい(気がする)
  • Array#include?は遅そう… (Hashにした方が良いかも)
  • 文字列の連結では << が速いらしい (参考 : ぞえ の戯れ言 - 文字列の連結)
  • Arrayを使っているので1つ目のファイルの行順に表示される

あぁ、やっぱりRubyって使いやすい気がする。この本買っちゃおうかしら。