仙石浩明の日記

2006年

2006年5月7日

面接FAQ: 「人の役に立つことをしたい」と言う技術者の卵たち hatena_b

これまで 「弊社の面接で(私に)よく聞かれること、 面接官自身が語る面接攻略法」を、 「面接 FAQ」シリーズで書いてきたのですが、 タイトルでは内容が分かりにくかったのではないかと反省する今日このごろなので、 今回から より内容を表わすタイトルに変えてみます (2007/7/12追記: 以前のページのタイトルも修正しました)。

内容自体は、「面接 FAQ」シリーズの続き物ですので、 以下のページも参照して頂けると幸いです。

(1) 高い技術力って例えばどんなことですか?
(2) 何か質問はありませんか?
(3) 前職でいくらもらっていましたか?
(4) 仮に、何をしてもいい、と言われたら、何をしますか?

続く 5 回目となる今回は前回の続きで、 「何をしたいか」と質問したときの、 応募者 (つまり面接を受けに来た人) の答についてです。

一生の時間のうちのかなりの時間を仕事に費やすのですから、 一生かけて取り組みたいと思うようなことをすべきだと私は思います。 嫌々仕事をしたって、好きでやってる人に勝てるはずはありません。 転職 (新卒の学生さんにとっては就職) を成功させる第一歩は、 自分が本当は何をやりたいか自覚することから始まるのだと思います。 だからこそ、私は面接で「何をしたいか」をしつこいくらい聞くのですが...

私にとっては意外なことに、 「人の役に立つことをしたい」 と
答える人が少なからずいます。

もちろん、「人の役に立つこと」という目的は崇高なものです。 NHK の番組「プロジェクトX」を引き合いに出すまでもなく、 人のために自らを投げうって技術革新に取り組む技術者たちの姿は 人の心を打つことでしょう。

でもそれって、平たく言えば、 技術者でない人 (視聴者) に技術者の人生が分かったような気にさせるための、 技術者でない人 (番組制作者) が考えたストーリーに他ならないわけです。 つまり、技術者の(技術者を描いた)、非技術者による、非技術者のための番組なわけで、 これから技術者としてスキルアップしていこうとする人が、 そんなマスコミの「嘘」を信じちゃいけません。

13歳のハローワーク 公式サイト では、 「人の役に立つのが好き」な人に勧める職業として、

  • 政治家, 公務員 などの行政職
  • 弁護士, 裁判官 などの司法職
  • 保育士, 小中学校教師 などの教育職
  • 警察官, レスキュー隊員 などの安全に関する職種
  • ソーシャルワーカー, ホームヘルパー などの福祉関係の職種

があげられています。 「人の役に立つ」のを第一の目標とするなら、 まず技術を身につけて、その技術で人の役に立つ、なんて 回りくどいことをせずに、 もっとストレートにこういった職業を目指すべきでしょう。 技術を身につける、なんてサブ・ゴール (sub goal) を設定していては、 ゴールを目指す前に人生終わっちゃいます。

「技術」をあくまで目的を達成するための一手段としてとらえるのなら、 自分で技術を習得するなんて遠回りをするより、 技術者を雇うなりアウトソースするなりして技術面は他の人に任せ、 自分は自分の目標に邁進すべきなのです。 「技術」と「人の役に立つこと」両方を追い求めるには人生は短すぎます。

技術者としての人生を歩もうとするなら、 まず自分がどれだけ技術が好きか、 とことん自分の気持ちを確認してみるべきだ、 と私は思います。

- o -

連休初日に、 「今日から五月連休ですね。会社によっては 9 連休のところもあるとか。あなたは何をしますか?」という問いかけをしました。 連休最終日の今日、連休を振り返ってみていかがでしょうか? 好きなことができましたか?

Filed under: 元CTO の日記,技術と経営,自己啓発 — hiroaki_sengoku @ 09:27
2006年5月6日

stone 2.3a リリース

stone 2.3a をリリースした。 stone 2.3 からの変更点は以下の通り。

二重 free するバグの修正

stone 2.3 における最も重大なバグ。 同時に大量に socket を close する、という利用パターンにて、 二つのスレッドが同時に同じ ExBuf に対して ungetExBuf を行なってしまうために、 二重 free が起きてしまう。 stone.c 2.2.1.11 で修正 (わずか一行の修正だが極めて重要)。

Pair を thread で処理しているときは scanClose 対象から外す。

thread 処理中は、そもそも close 要求が出ていないわけで、 当然 scanClose 対象から外れるはず、と思っていたが、 thread 処理中に proto_close をセットすることもあるわけで、 dowrite が ungetExBuf を呼ぶタイミングと、 scanClose が freePair を呼び出して ungetExBuf を呼ぶタイミングとが 重なる可能性があると思われるので、その防止策。

socket を close しないバグの修正

SSL_accept に失敗したとき、すなわち TCP レベルでの accept は成功したが、 続く SSL ハンドシェークで失敗した時に、 中継先の socket が open しっぱなしになるバグ。 これは、中継元 socket の accept に成功した時点で、 中継先の socket を open するが、 SSL_accept に失敗したときに close していなかったために起きた問題。 stone.c 2.2.1.8 で修正 (これもわずか一行の修正だが重要)。

SSL ハンドシェークを行なわない TCP レベルのヘルスチェックを stone の中継元ポートに対して行なったとき、 stone が open した socket がどんどん増え、 too many open files エラーが起きて accept 出来なくなったことにより発覚。

SSL_accept に失敗したとき、pair2 側に close 要求出すのを忘れていた。 SSL_accept に時間がかからない時 (localhost からの接続時) は、doaccept 内で freePair するので問題にならないが、 SSL_accept に時間がかかる時は、doReadWrite 内で p[i]->proto |= proto_close; するので、この時 p[1-i]->proto |= proto_close; も行なう必要がある。

epoll 対応 (select 版も大幅に性能向上)

select(2) の代わりに epoll(2) も利用できるようにした。 コンパイルオプション「-DUSE_EPOLL」をつけることにより epoll 版を make できる。 linux 2.6 以上のカーネルと、 glibc 2.3 以上が必要。

epoll 対応のため、stone のコード全体の見直しを行なった。 結果、(epoll を使わない) select 版も大幅にパフォーマンスが向上した。 「stone 2.3 が遅い理由」および 「stone 2.3a (候補) ベンチマーク」を参照。

アドレスキャッシュ機能の実装

Windows 2000 などで、gethostbyname が遅いという問題があるようなので、 proxy で resolv した IP アドレスをキャッシュする。 stone.c 2.2.2.11 で実装。

unix domain socket で uid 等の取得

SO_PEERCRED 参照。

  <host>:<port>/http <sport> <request> [<xhost>...]

http リクエストにのせて中継します。<request> は、 HTTP 1.0 で規定されるリクエストです。 リクエスト文字列中、 「\」はエスケープ文字であり、 次のような置き換えが行なわれます。

  \u   接続元のユーザID (番号)
  \U   接続元のユーザ名
  \g   接続元のグループID (番号)
  \G   接続元のグループ名

stone.c 2.2.2.18 で実装。

ミリ秒単位のログ出力

stone.c 2.2.2.12 および stone.c 2.2.2.17 で実装。

health HELO コマンドの分割

stone.c 2.2.2.13 で実装。

Filed under: stone 開発日記 — hiroaki_sengoku @ 20:54
2006年5月6日

VPN-Warp 応用編 (1)

VPN-Warp の使い方の基本的なところを、
VPN-Warp 入門編 6 回シリーズで説明してきました:

  • 入門編 (1) VPN-Warp の特長: ふつうの SSL VPN と比べて
  • 入門編 (2) VPN-Warp の特長: ssh のポートフォワードと比べて
  • 入門編 (3) stone & relayagent の設定方法
  • 入門編 (4) stone の代わりに OpenSSL の s_client を使ってみる
  • 入門編 (5) stone の代わりに普通の WWW ブラウザを使ってみる
  • 入門編 (6) WWW ブラウザを使う場合の注意点

これからの「VPN-Warp 応用編」では、VPN-Warp を利用した実運用例を 紹介していきます。VPN-Warp は、 KLab(株) および KLabセキュリティ(株) のネットワーク インフラを支える 基盤技術の一つとなっており、 様々な用途に使われていて、 かつ長期にわたる安定運用実績があります。

とはいっても、実際に VPN-Warp を利用してみないことには 絵に描いた餅でイマイチ実感がわかないと思いますので、 この「仙石浩明CTO の日記」の読者のみなさま限定で、 VPN-Warp 試用ライセンスをご提供します。

試用をご希望のかたは、 vpnwarp@klab.org 宛に、

  • どのように VPN-Warp を利用してみたいか
  • 「VPN-Warp入門編」を読んだ上での質問 and/or 感想
  • 簡単な自己紹介

を送って頂ければ、relayagent (Windows 用 or Linux 用) と、 VPN-Warp を利用するための SSL証明書をお送りします。 試用期間はとりあえず一ヶ月程度を考えていますが、 ご利用方法によっては延長も可能です。 また、商用ライセンスをご希望のかたは別途ご相談下さい。

なお stone については、 stone 公式ページ から Windows 用のバイナリ、 ないし Linux 用などのソースコードをダウンロードできますので、 そちらをご利用下さい。

Filed under: システム構築・運用 — hiroaki_sengoku @ 07:46
2006年5月5日

su & emacsclient hatena_b

普段 emacs を使っている人に質問なのですが、
root 作業するときどうしてますか?

私は、GNU Screen の中で emacs をずーっと立ち上げっぱなしにしていて、 ほとんどの作業を emacs の中で行なっています。 もちろんコマンドラインから何かを実行するときも、 emacs の shell モード (正確に言うと j-shell.el なんですが ^^;) の中で 行なっています。

いきおい、root になるときも shell モードで「su」を実行することになります。 で、root 権限でファイルを読み書きしようとしたとき、 どうするのがいいか、というのが問題です。

そんなの root で emacs を実行しておけばええやん、 という声が聞こえてきそうですが、 root 権限で常に emacs が動いている、というのは 想像するのもおぞましいですし、かといって 編集するたびに root で emacs を立ち上げるのは、 (起動に時間がかかるので) もっと嫌です。 そもそも root 権限で emacs (に限らずエディタならなんでも) を 立ち上げるには、 Screen の別ウィンドウで行なわなければならず、 ウィンドウの切替で作業がかなり煩雑になってしまいます。

ちなみに、ずーっと以前は、ange-ftp を使っていました。 つまり、emacs から root@localhost へ ftp して (root の) ファイルを読んできて編集し、 保存するときも ftp で書込む。 この方法は root でログインする、 という気持ち悪さがもともとあったので、 ftpd を走らせなくなったのを機会に止めました。

で、それ以来使っているのが、 今回紹介する emacsclient を使う方法です。 もしもっといい方法があるぞっ、というかたがいらっしゃいましたら、 是非教えてください (_O_)。

- o -

emacs 上で「M-x server-start」と入力すると、 emacsserver (gnuserv) を 走らせておくことができます。 この状態で、コマンドラインから

% emacsclient ファイル名

などと emacsclient (gnuclient) を実行すると、 引数に指定したファイルを、emacs で編集することができます。

したがって、root 権限でファイルを編集するときは、 まずファイルを emacs で読み書きできるようテンポラリファイルへコピーし、 それを emacsclient で開き、 テンポラリファイルが変更されたら、 それを元のファイルへ root 権限で書き出せばよいことになります。

私は suemacs と名付けた以下のような perl スクリプトを書いて使っています。 例えば

# suemacs /root/.cshrc

などと実行すると、 /root/.cshrc の内容が /tmp/suemacs5544/.cshrc_0 へコピーされ、 emacsclient /tmp/suemacs5544/.cshrc_0が実行されます。

suemacs スクリプト

#!/usr/bin/perl
$user = $ENV{'LOGNAME'};
$tmp = "/tmp/suemacs$$";
$Debug = 0;
$Once = 0;
use POSIX ":sys_wait_h";
use Getopt::Std;
getopts('do') || &help;
$Debug = 1 if $opt_d;
$Once = 1 if $opt_o;

sub help {
    print <<EOF;
Usage: suemacs <opt> <file>...
opt:  -o        ; write once on closing
      -d        ; for debug
EOF
    exit 1;
}

if ($> != 0 || ! $user) {
    exec "emacsclient", @ARGV;
}
($login, $pass, $uid, $gid) = getpwnam($user) or die;
print "login: $login,  uid: $uid,  gid: $gid\n" if $Debug;
umask 077;
mkdir $tmp || die;
chown $uid, $gid, $tmp;

for ($i=0; $i < @ARGV; $i++) {
    my $tmpfile = $ARGV[$i];
    $tmpfile =~ s@.*/@@;
    $tmpfile = "$tmp/${tmpfile}_$i";
    push @argv, $tmpfile;
    my $mtime = &cp($ARGV[$i], 0, $tmpfile);
    push @mtime, $mtime;
    chown $uid, $gid, $tmpfile;
}
if (!$Debug) {
    if (!fork) {
        close(STDOUT);
        close(STDERR);
        open(">&STDOUT", "/dev/null") || die;
        open(">&STDERR", "/dev/null") || die;
    } else {
        exit 0;
    }
}
if (!fork) {
    ($(, $)) = ($gid, $gid);
    ($<, $>) = ($uid, $uid);
    exec "emacsclient", @argv;
    exit 0;
}
my ($ret);
do {
    sleep 1;
    $ret = waitpid(-1,WNOHANG);
    print "ret: $ret, status: $?\n" if $ret > 0 && $Debug;
    for ($i=0; $i < @argv; $i++) {
        $mtime[$i] = &cp($argv[$i], $mtime[$i], $ARGV[$i])
            if $ret > 0 || ! $Once;
        unlink $argv[$i] if $ret > 0;
    }
} until ($ret > 0);
rmdir $tmp;
exit 0;

sub cp {
    my ($src, $stime, $dst) = @_;
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
        $atime,$mtime,$ctime,$blksize,$blocks) = stat($src);
    if ($stime == $mtime) {
        print "$src is not modified.\n" if $Debug;
        return $mtime;
    }
    print "cp $src $dst\n" if $Debug;
    open(SRC, $src) || die;
    open(DST, ">$dst") || die;
    while (<SRC>) {
        print DST;
    }
    close(DST);
    close(SRC);
    utime $atime, $mtime, $dst;
    return $mtime;
}
Filed under: プログラミングと開発環境 — hiroaki_sengoku @ 19:35
2006年5月5日

自己評価をしよう

技術者にとっても、無知の知が重要、という話は以前にも書いた。 自分が何が分かっていないか正確に把握していれば、 適宜質問したり、勉強したりできる。 自身の実力がどのくらいか正確に把握できていれば、 判断を誤まらずに済む。

過去の成功体験がアダとなって失敗した、という話をよく聞く。 過去とは状況が変わっているのに、 今でも自分が正しい判断をできると思い込み、 過去の手法を現在に適用してしまう。 過去の成功体験を過剰適用してしまっている。

自信を持つことは悪いことではないが、 ともすると自信過剰に陥る。 自分の実力をどこまで正確に評価できるかが鍵だろう。 努めて自分の短所を見るようにしなければならない。

自分の短所は見つけにくい。 ついつい長所ばかりを見てしまう。 一方、他人の短所はよく見えるもの。 ならば逆に、無理矢理にでも他人の長所を見つけよう。 そうすれば、自分が何ができていないか見えてくるだろう。

Filed under: 技術者の成長 — hiroaki_sengoku @ 08:19
2006年5月4日

同時に考えよう (5)

アナロジーで考えると理解しやすいのはなぜだろう?

新しい事柄を、すでに理解している事柄に対応付けると、 分かったような気になりやすいので注意が必要であるが、 どこまで類似していて、 どこが違うかはっきり意識しながら考えるのであれば、 アナロジーは素早くモノゴトの本質を見極める方法として有効である。

すでに完全に理解している事柄というのは、 頭の中にその事柄に対応する「回路」が出来上がっていて、 無意識の思考で高速に考えることができる。 一方、初めて見聞きする新しい事柄は、 当然のことながら頭の中にはなにも準備ができていないから、 無意識の思考で考えるのは難しく、 意識した思考で考えることになる。つまり非常に遅い。

ここで、もし新しい事柄の一部でも、 無意識の思考で考えることができるのなら、 つまりすでに理解している事柄と似ている点があるのなら、 その部分は無意識の思考に任せることができて、 理解を加速することができるのではないか。 おそらく、これがアナロジーで考えるということなのだろうと思う。

このように考えていると、 マービン・ミンスキーのフレーム理論が思い起こされる。 つまり知識の枠組みができている事柄についての理解が速い、 ということと似ているが、 単なる知識の整理にとどまらず、 無意識の「自動化された」思考によって、 新しい事柄への理解が加速されるのではないか。

その一方で、過剰な類推をしてしまう、 つまり無意識への「丸投げ」をしてしまうのが、 「分かったつもり」であって、 頭の中の回路がどこまで正しいのか、 つまり「どこまで類似していて、どこが違うか」を検証する必要がある、 ということなのだろう。

その検証を、意識して行ってもよいのだが、 検証を無意識の思考で行うことができれば、 さらに強力だろう。 これは、比喩のやりすぎや、論理の誤謬を「直感で」感じられる人は、 検証を実際に無意識の思考で行っているのだろうと思う。

Filed under: 技術者の成長,自己啓発 — hiroaki_sengoku @ 20:14
2006年5月4日

プログラマ 35歳 定年説 hatena_b

「プログラマ 35歳 定年説」、みなさんも一度は 聞いたことがあるのではないかと思います。 35歳ぐらいになったらプログラミングなんて仕事は若い人に譲って、 マネジメントをやりなさい、 という趣旨ですね。

その理由として、体力的な面だとか、 歳をとってくると新技術を覚えられないとかが、 あげられるようです。 実際、多くの企業でプログラミングは新人ないし外注の人 (最近はオフショアも増えてきました) の仕事とされ、 中堅社員はマネジメントや上流行程を担当することが多いようです。

そして、この定年説に真っ向から異を唱える主張が、 ここ 10年くらいずーっと続いています。 よくもまあ、こんなに長い間、 アンチ定年説が唱え続けられるものだと感心してしまいますが、 やはりこれは定年説に異を唱える人が多いにもかかわらず、 世間一般では 35歳を越えるあたりで、プログラマ人口が減るからなのでしょう。 寄る年波には勝てず、とか上司の強い勧め、とかで 泣く泣く (?) プログラミングの現場から離れる人が多いのでしょう。

というわけで、よくあるアンチ定年説を今さら唱えても仕方がないので、 ここでは逆に、あえて肯定してみることにします。

高度な知的労働は、歳をとってもできる傾向があるようです。 長年の経験が役に立つから、 老化する部分を補える というわけですね。 医師や弁護士、芸術家に音楽家、棋士や料理人などなど。 この伝でいけば、プログラマも仲間に入れてもらえそうな感じがしますし、 実際 35歳を越えても若い人を圧倒するパフォーマンスで コーディングしている人も大勢います。 だからこそ「プログラマ 35歳 定年説」は間違いだ、 と声高々にみなさん主張されるのでしょう。

しかし、もし他の「高度な知的労働」においても「定年」が存在したとしたら?
プログラマにも定年があってもおかしくはないのではないでしょうか?

そんなバカな。外科医とかだと手元が狂うと危ないから、 自主的な引退はあるかも知れないけれど、 弁護士や芸術家に定年があるなんて聞いたことがないぞ。

確かにその通りなのですが、 医師や弁護士は国家試験に合格しなければなることができません。 国家試験に合格できずに医師や弁護士になるのをあきらめる人も沢山います。 将棋の棋士はもっと厳しくて、23歳までに初段になれないと 奨励会を退会しなければなりません。 芸術家や料理人は、腕を認められなければ食べるのにも困ってしまうかも知れません。

それが何か? 定年のあるなしと関係あるの?

いえ、ですからプログラマの 35歳というのは、 医師や弁護士にとっての国家試験であり、 奨励会の棋士にとっての 23歳であり、 芸術家や料理人にとっては、師匠に認められるか否か、 ということなのではないでしょうか? つまり、他の「高度な知的労働」の職種では、 定年うんぬん以前に、そもそもその職業につくこと自体に壁があります。 どんなにその職業に従事したいと思っても、 国家試験に合格できなかったり、 初段になれなかったり、 師匠に認められて弟子にしてもらえなかったりすれば、 35歳よりもっと早い段階でその道をあきらめることが多いのではないでしょうか。

確かにプログラマってのは、ピンからキリまでいて、 キリのほうは際限がないなぁ。 本当は 35歳といわず、もっと早い段階でプログラマには向いてないことを 自覚したほうが幸せなのではないか、と思うような人もいるよなぁ。

ですよね? こういっちゃミもフタもないんですが、 いくらプログラミングの才能がない人でも、 体力さえあればそれなりに役にたっちゃうことも多いんですよね。 ぶっちゃけ、レベルが低いプログラムでもいいなら、 プログラミング言語の文法を一通り知ってさえいれば誰でも書けちゃいますし、 デバッグ環境さえ整っていれば、デバッグもなんとかなる。 もちろん時間は優秀なプログラマの何倍、何十倍もかかりますけどね。 そもそもこの業界、慢性的な人手不足ですから、 猫の手でもいいから借りたい。 何十倍も仕事が遅くてもいいから手を借りたい。 ただ、あくまで「猫」なんで、デスマーチに耐えられる体力はないと困る。

なるほど。そういえば「アンチ・プログラマ35歳定年説」を唱える人って、 優秀なプログラマが多いような気がする。 優秀ならばパフォーマンスは体力とは関係ないから、 35歳なんて関係ないと言い切れる。 そもそもプログラマに向いている人と、まるで向いてない人が、 いっしょくたに「プログラマ」と呼ばれているのが問題なのか。

そういうことなんだと思います。 プログラマに向いているか向いていないか判断できない人が、 プログラマの上司をやってる、 というのも一因でしょうね。 医師, 弁護士, 棋士などには、足切りのための絶対的な評価基準がありますし、 芸術家や料理人などには、師匠がいて、能力の有無をきちんと判断してくれる。 プログラマだけが基準なしに、ただ若いというだけで、 いっぱしのプログラマ気取りになってしまう。 師匠がいれば、「プログラマを気取るのは百万年早い!」って 怒鳴ってくれるんでしょうけどね。

まとめると、 プログラマには少なくとも二種類あって、 他の「高度な知的労働」の職種と同様に定年がない「優秀なプログラマ」と、 若いだけが取りえの「見習いプログラマ」とを、 きちんと区別する必要があると。 で、「見習いプログラマ」は 35歳といわず、もっと早い段階で、 プログラミングに向いてないことを自覚し、 もっと自分に向いている仕事を見つけるべきだ、ということですね。

そして「優秀なプログラマ」には、 きちんとその能力を評価し、育てていく環境を整えるべきでしょう。 私の感覚では、本当にプログラミングに向いている人って、 20人に一人くらいしかいないように思えるんですよ。 だからこそ、そういう希有な人材には最高の環境を提供してあげたい、と思います。

Filed under: 元CTO の日記,技術と経営 — hiroaki_sengoku @ 17:06
2006年5月3日

昭和記念公園

五月連休ということで、今日は妻と一緒に昭和記念公園へ行ってきました。 自宅から近いということもあり、よく行く公園です。 自宅から南武線で立川駅まで行き、青梅線に乗り換えて一駅、 西立川駅で降りると、すぐ昭和記念公園の西立川口です。

ところが今日は青梅線に乗った段階でいつもと違う... いつになく混みあっています。 これはもしかしてみんな昭和記念公園? 悪い予感は的中し、 昭和記念公園の西立川口の前には人人人... おまけに入場券の券売機の前には長蛇の列が... ;-(

私も妻も大阪育ちなので、とても「いらち」です。 とてもじゃないけどこんな長蛇の列に並んで待つなんてことは不可能です。 残念ですが公園に入るのはあきらめることにしました。

ところが、今日は来る途中のスーパーで買った弁当を持ってきていました。 すぐ食べないと悪くなってしまいます。 どこかに食べるところはないかと、 公園の外周に沿って立川の方へ歩き始めました。 すると、自転車専用の入口がありました。 自転車で来た人はここから公園へ入れるようです。

ということは、もしかして入場券の券売機もある?

はやる心を抑えて入口のところまで行ってみると、 確かに券売機があって、別に自転車専用というわけでも無さそうです。 無事、入場券二枚を get ! しました。 西立川口に戻って、長蛇の列を尻目に公園に入ることができました!

昭和記念公園 ポピー花畑

アイスランドポピー満開の花畑の向こうに、 人で埋め尽された「みんなの原っぱ」が見えます。 11ヘクタールもある原っぱが、こんなに人だらけになるとは...

Filed under: その他 — hiroaki_sengoku @ 22:19
2006年5月3日

勝ち組になるには

先月 4/14 19:30 NHK で、特報首都圏「就職戦線異状あり・格差社会の不安」と 題する番組があった。 新卒の学生さん達が「勝ち組になる」ことを目指して 就職活動を行なっているのだという。

そりゃ、勝てるものなら勝ちたいと思うのは人の常なので、 これから社会に出ていこうとする学生さん達が、 将来勝ち組になれるような就職先を選ぼうとするのは至極当然のことだと思う。

ところが

学生さん達曰く、「勝ち組になるため、儲かっている会社に就職したい」。

オイ、それは根本的に間違ってるぞ、と言いたくなる。 儲かっている会社、それはお金を産み出す仕掛けが確立している会社である。 つまりできるだけ属人性を排した、回り続ける仕掛けが確立している会社である。 このような会社が新卒採用を行なうのは、 仕掛けを維持するために「歯車」の補充を行なうためである。

すでに出来上がっている仕掛けは、それが陳腐化するまでは、動き続けるだろう。 動き続けるには若い人を採用し育てることが必須だから、 そういうことまでも含んだ仕掛けである。 このような仕掛けに参加すれば、 仕掛けの中で活躍できるレベルまで育ててもらえるし、 仕掛けが動き続ける限りは安泰である。

しかしそれは「勝ち組」ではない。 勝ち組なのは、放っておいても回り続けるその仕掛けを作り上げた人であって、 いったん仕掛けが回りはじめたとき、 その人はその仕掛けの中にはいないはずである。 それが「属人性を排する」という意味である。 「属人性を排する」すなわち「置き換え可能」な人材で 仕掛けを回すことであり、 置き換え可能な人材は「勝ち組」では有り得ない。

したがって、勝ち組になるには、 出来上がって回りはじめた仕掛けに歯車として参加するのではなく、 そういう仕掛けが無いところに参加し、 仕掛けを作る側にまわらなければならない。

もちろんどうやって仕掛けを作るべきか最初は分からないだろう。 当然、失敗することもある。 しかしそうやって無から有を作り出す経験を積んだ人材は決して、 置き換え可能ではない。 仕掛けを作り上げようと悪戦苦闘する組織は、属人性のカタマリである。 将来の勝ち組になるには、 そういった組織の中に身を置かねばならない。

More...
Filed under: 技術と経営 — hiroaki_sengoku @ 07:52
2006年5月2日

VPN-Warp 入門編 (6)

前回は、WWW ブラウザで VPN-Warp へアクセスする方法を紹介しました。 基本的には https://リレーサーバのホスト名/パス という URL を WWW ブラウザでアクセスすれば、 relayagent に設定したフォワード先の WWW サーバへつながるわけですが、 二点ほど注意すべき点があります。

  1. 「https」と「http」の違い
  2. ホスト名の違い

WWW サーバに直接アクセスする場合の URL を、 例えば http://intra/path としましょう。 この場合、WWW サーバへ送られるリクエストヘッダは、 次のようになります(説明の都合上大幅に単純化しています):

GET /path HTTP/1.1
Host: intra

一方、VPN-Warp 経由 (つまり、ブラウザ → リレーサーバ → relayagent → WWW サーバ) でアクセスする場合に、 WWW サーバへ送られるリクエストヘッダは、URL が https://relay.klab.org/path ですから、 次のようになります:

GET /path HTTP/1.1
Host: relay.klab.org

両者を比べると、「Host: 」フィールドの部分が異なりますね。 多くの WWW サーバには、バーチャルホストと呼ばれる機能があって、 一台の WWW サーバで、いろんなホスト名の URL を受け付けて、 ホスト名に応じて異なるページを見せることができるわけですが、 要はこの「Host: 」フィールドを見て、 配信するコンテンツを切替えているわけです。

したがって、「Host: relay.klab.org」のままでは、 「intra」のコンテンツを見ることができないケースが多いでしょう。 この (2) ホスト名の違い の問題を解決するために、 relayagent には「Host: 」フィールドの書き換え機能があります。 この場合でしたら、 「-h intra」オプションを追加指定することにより、 リクエストヘッダの「Host: 」フィールドを「intra」に書き換えます。

これで少なくともリクエストヘッダは、直接アクセスする場合と、 VPN-Warp 経由でアクセスする場合が同じになりました。 コンテンツの HTML 文書で URL を絶対指定したりしない限りは、 普通にブラウズできるでしょう... か?

コンテンツが全て静的なページから構成されているのであれば、 その通りなのですが、 普通は PHP や Java などを使用して動的に生成するページもあるでしょう。 その場合、PHP インタプリタやサーブレットコンテナは、 いま生成中のページの URL がなんであるか把握していて、 ページを出力するときに、絶対指定を出力してしまうケースがあるので 注意が必要です。

特に、(1) 「https」と「http」の違い は留意しておくべきでしょう。 WWW サーバにとっては、80番ポートでアクセスを受け付けているわけですから、 PHP インタプリタないしサーブレットコンテナは、 「http」なページを出力中だと認識しているはずです。 ところが WWW ブラウザにとっては、アクセスしている URL は「https」です。 WWW サーバがレスポンス中に「http」な URL を出力してしまうと、 ブラウザはその URL をたどれなくなります。

とはいえ、動的なページを出力する際に絶対 URL を指定してしまうと、 コンテンツの URL を変更したいときも不便ですし、 そもそも相対 URL で済むところをわざわざ絶対指定する必然性もないので、 普通の Web アプリケーションを使う限りはあまり問題とはならないようです。 WWW サーバのレスポンスを全て監視して、 適宜 URL の書き換えを行なえば解決できる問題ではあるのですが、 ごく少数の絶対 URL の書き換えのためだけに、 全てのレスポンスを監視するのは、 パフォーマンスの点で割が合わないと言えそうです。

むしろ問題となるのは、リダイレクトです。 リダイレクトの場合、WWW サーバは次のようなレスポンスヘッダを返します (説明のため大幅に単純化しています):

HTTP/1.1 301 Moved Permanently
Location: http://intra/path

このような 301 レスポンスを受け取ると、 WWW ブラウザは「Location: 」フィールドで指定された URL のページを 表示しようとします。 そして「Location: 」フィールドは絶対 URL で指定されます。

WWW サーバは、ページの URL は「http://intra/...」である と認識していますから、 同じホスト名の URL へリダイレクトを行なう場合 「Location: 」フィールドには「http://intra/...」が指定されます。 これをそのまま WWW ブラウザに伝えてしまうと、 WWW ブラウザは「http://intra/...」すなわち VPN-Warp を介さずに 直接 WWW サーバへアクセスしようとしてしまいます。

この問題を解決するために、 relayagent にはレスポンスヘッダの「Location: 」フィールドを書き換える機能が あります。 すなわち「-H」オプションを指定すると、 「Location: 」フィールドのホスト名とフォワード先のホスト名 (今回の例の場合は intra ですね) が一致する場合、 それをリレーサーバの URL へ書き換えます。 前述したレスポンスヘッダの場合であれば、

HTTP/1.1 301 Moved Permanently
Location: https://relay.klab.org/path

に書き換えるわけです。これでめでたく WWW ブラウザは リダイレクト先のページも、VPN-Warp 経由でアクセスするようになります。

Filed under: システム構築・運用 — hiroaki_sengoku @ 15:13
2006年5月2日

健全なニート

人は働かねばならない、と誰が決めたんだろう?
働かなくても生活できるなら、働く必要はないわけで、
ただ単に、勤労は国民の義務だ、なんて言っても意味がない。

[技術]Agdaの紹介Flashから引用:

定理証明ツールが動いているところ、生まれて初めて見た。感動。自分もつかってみたいが、ドカタSEなのでそれよりやらないといけないことがあるのが悲しいところ。資産が50億ぐらいあったらニートになって毎日こんなことばっかりしたいな。これ、俺の夢(←駄目人間)。

50億もなくても生活できると思うが、 まったくお金を稼がずに生活しようと思うと、 数億は必要だと思われるので、 なかなか簡単ではない。

しかしながら、 技術者の生産性には人によって 3桁の差があるわけで、 並みの技術者の数倍の生産性がある人なら 大勢いるだろう。 そういう人たちにたとえば一日3~4時間だけ 会社の利益に直結することをしてもらって、 一日の大半の時間は好きなことをやってもらう。 もともと生産性が高い人なら、3~4時間の労働だけでも、 生活に困らないくらいの給料を出すことができる。

ほとんどの時間、 引きこもって好きなことをしているように見えるわけで、 まあニートみたいに見えるかもしれないが、 向いてない仕事を嫌々やるより、 好きなことをとことん突き詰めるほうが、 よほど健全だと思うし、 実は社会への貢献度合いも高くなる可能性が高い と思うのだが、どうだろうか。

Filed under: 技術と経営 — hiroaki_sengoku @ 12:08
2006年5月1日

EPOLL_CTL_ADD を行うタイミング

epoll版 stone で EPOLL_CTL_ADD を行うタイミングについて。

基本的には doAcceptConnect 内で行う。 つまり、中継元からの接続を accept し、 中継先へ connect するルーチン。 このルーチンは、Pair リストへの挿入 (insertPairs) を行う 唯一のルーチンでもある。 このルーチンで EPOLL_CTL_ADD を行わないのは、以下の場合のみ。

(1) proto_close フラグが立っている場合
エラー等の原因で close 要求が行われている場合、 中継の必要がないから、EPOLL_CTL_ADD も行わない。
(2) 中継先の Pair かつ proto_noconnect フラグが立っている場合
proto_noconnect フラグが立っているのは 中継を行わない Pair であることを示す。
この場合は、EPOLL_CTL_ADD を行う必要はない。

ところが、stone を proxy として用いるとき、 proto_noconnect フラグが立つ。 これは中継元からリクエストヘッダを受け取るまでは 中継先が決まらないため。 このとき、(2) に該当するので EPOLL_CTL_ADD が行われない。

このため、stone.c 2.2.2.20 では、proxy として用いたとき EPOLL_CTL_ADD が行われず、 中継先への connect 完了を検知できなくなってしまっていた。

中継先が決まった段階で EPOLL_CTL_ADD を行うよう修正。

$Id: stone.c,v 2.2.2.21 2006/04/29 07:32:28 hiroaki_sengoku Exp $

Filed under: stone 開発日記 — hiroaki_sengoku @ 18:24
2006年5月1日

livedoor blog 生ログ取得スクリプト (2)

昨日書いた「livedoor blog 生ログ取得スクリプト」を使って、 毎日前日の生ログを取得する cron を仕掛けておいたのですが、 月が代わった途端に問題発覚(^^;)。 livedoor blog の生ログって、月単位なのですね。 つまり、生ログを参照するページの URL は、

http://analyzer.livedoor.com/log/raw?page_id=22222&y=2006&m=4

などとなっていますが、「m=4」を指定しない限り 4月分のログは表示されない (今月分のみ表示される)、 という仕様のようです。 livedoor blog の有料プランを始めてから初めての月代わりだったもので...

というわけで、生ログを取得する際に日付を指定できるように修正してみました。

livedoor.pl gcd date 2006-04-30 raw_log 100 -

などと、「date YYYY-MM-DD」ないし「date YYYY-MM」を指定することにより、 生ログを取得する日付ないし月を指定できます。 「raw_log 100 - 」は、生ログ 100ページ分 (2000行) を標準出力 (「 - 」) に出力する、という意味です。

生ログを遡っていく途中で、指定した日付と異なる日付の生ログが得られたら、 それ以上の取得をストップするので、ページ数は多めに指定しておいて大丈夫です。 また、ついでに「ブログのエクスポート(バックアップ)」もサポートしました。

livedoor.pl gcd export_reserve

で、「エクスポートファイルの作成」を行ない、

livedoor.pl gcd export バックアップファイル名

で、エクスポートファイルをダウンロードして 「バックアップファイル名」で保存します。

以上の機能は、 livedoor blog の有料プランを選択していないと使用できませんが、 スタイルシート(CSS)および HTMLのテンプレートの取得は、 ブログのデザインを custom に設定していれば使用できます。

まず、livedoor blog の管理ページで、 「カスタマイズ/管理」-「デザインの設定」を選び、 デザインとして「カスタマイズ」を選択したときの URL を確認します。 例えば

http://cms.blog.livedoor.com/cms/design/edit?blog_id=1600549&id=11111

といった感じの URL になると思います。 「blog_id=1600549」および「id=11111」の数字を、 スクリプト先頭の

"BlogID" => 1600549,
"ID"     => 11111,

の部分に設定しておいて、

livedoor.pl gcd css CSSファイル名

と実行すれば、スタイルシート(CSS) が「CSSファイル名」で保存されます。 「css」の代わりに、 「index_tmpl」「article_tmpl」「category_tmpl」「monthly_tmpl」 を指定すれば、それぞれ「トップページ」「個別記事ページ」 「カテゴリアーカイブ」「月別アーカイブ」の HTMLテンプレートを 保存できます。 一度のスクリプト実行で一括して保存することもできます:

livedoor.pl gcd css blog.css index_tmpl blog.html \
        article_tmpl blog_article.html \
        category_tmpl blog_category.html \
        monthly_tmpl blog_month.html

「スタイルシート(CSS)」を、「blog.css」へ、
「トップページ」を、「blog.html」へ、
「個別記事ページ」を、「blog_article.html」へ、
「カテゴリアーカイブ」を、「blog_category.html」へ、
「月別アーカイブ」を、「blog_month.html」へ、
それぞれ保存します。

livedoor.pl (livedoor blog 生ログ & CSS/テンプレート 取得スクリプト)

#!/usr/bin/perl
use LWP::UserAgent;
use HTTP::Request::Common;
use CGI qw/unescapeHTML/;

%blogs = (
    "gcd" => {
        "User"   => "hiroaki_sengoku",
        "Pass"   => "xxxxxxxx",
        "BlogID" => 1600549,
        "ID"     => 11111,
        "PageID" => 22222,
    },
    "klab" => {
        "User"   => "klab_sengoku",
        "Pass"   => "yyyyyyyy",
        "BlogID" => 1631449,
        "ID"     => 33333,
        "PageID" => 44444,
    },
);

&help unless $_ = shift;
if (exists $blogs{$_}) {
    my $blog = $blogs{$_};
    $User =   $$blog{"User"};
    $Pass =   $$blog{"Pass"};
    $BlogID = $$blog{"BlogID"};
    $ID =     $$blog{"ID"};
    $PageID = $$blog{"PageID"};
} else {
    &help;
}

$ua = new LWP::UserAgent;
$ua->agent("Mozilla/5.0 (Windows; U; Windows NT 5.1; ja)");
$ua->env_proxy();
$ua->cookie_jar( {} );
my $res = $ua->request(POST "http://member.livedoor.com/login/index",
                       [ "livedoor_id" => $User, "PASSWORD" => $Pass,
                         ".next" => "", ".sv" => "" ]);
while (my $type = shift) {
    if ($type eq "date") {
        $_ = shift;
        if (/^(\d\d\d\d)-(\d\d)(?:-(\d\d))?$/) {
            $Year = $1;
            $Month = ($2 + 0);
            $Date = $_;
        } else {
            die "date must be YYYY-MM-DD: $_\n";
        }
    } elsif ($type eq "css" ||
             $type eq "index_tmpl" || $type eq "article_tmpl" ||
             $type eq "category_tmpl" || $type eq "monthly_tmpl") {
        my $file = shift;
        open(OUT, ">$file") || die;
        my $url = "http://cms.blog.livedoor.com/cms/design/edit"
            . "?tmpl=$type&blog_id=$BlogID&id=$ID";
        my $req = new HTTP::Request GET => $url;
        my $res = $ua->request($req);
        if ($res->content =~
            /\<textarea .*name=\"content\" [^\>]*\>([^\<]+)\<\/textarea\>/) {
            my $content = unescapeHTML($1);
            $content =~ s/\r\n/\n/g;
            print OUT $content, "\n";
        }
        close(OUT);
    } elsif ($type eq "raw_log") {
        my $npage = shift;
        ($npage =~ m/^\d+$/ && $npage >= 1) || &help;
        my $file = shift;
        &help unless $file;
        open(OUT, ">$file") || die;
        my $url = "http://analyzer.livedoor.com/log/raw?page_id=$PageID";
        if ($Date) {
            $url .= "&y=$Year&m=$Month";
        }
        my $prepat = '\<td\b[^\>]*\>\<strong\>\<small\>';
        my $postpat = '\<\/small\>\<\/strong\>\<\/td\b[^\>]*\>';
        my $datematch = 0;
        pages: for (my $i=1; $i <= $npage; $i++) {
            my $req = new HTTP::Request GET => "$url&p=$i";
            my $res = $ua->request($req);
            my $datepat = '\d\d\d\d\-\d\d\-\d\d \d\d\:\d\d\:\d\d';
            my $date;
            for (split(/(\<small\>$datepat\<\/small\>)/o, $res->content)) {
                if (/^\<small\>($datepat)\<\/small\>$/o) {
                    $date = $1;
                } elsif (/^\<\/th\>\s*\<\/tr\>\s*/) {
                    my @record;
                    for (split(/\<\/tr\>\s*/, $')) {
                        my $column;
                        if (/$prepat(.*)$postpat/o) {
                            if ($1 eq 'URL') {
                                $column = 0;
                            } elsif ($1 eq 'リファラ') {
                                $column = 1;
                            } elsif ($1 eq 'ブラウザ') {
                                $column = 2;
                            } elsif ($1 eq 'リモートホスト') {
                                $column = 3;
                            } else {
                                die "Unknown column: $_\n";
                            }
                        }
                        if (/\<td\b[^\>]*\>\<small\>(.*)\<\/small\>\<\/td\b[^\>]*\>/){
                            $_ = $1;
                            s/\<\/?a\b[^\>]*\>//g;
                            if (/,/) {
                                s/\"/\"\"/g;
                                $_ = "\"$_\"";
                            }
                            $record[$column] = $_;
                        } elsif (/^\<\/table\>/) {
                            last;
                        } elsif (! /^\<tr\>\s*\<th\b[^\>]*\>/) {
                            die "Unknown format: $_\n";
                        }
                    }
                    if (! defined($Date) || $date =~ /^$Date/) {
                        $datematch = 1;
                        print OUT $date, ",", join(',', @record), "\r\n";
                    } elsif ($datematch) {
                        last pages;
                    }
                }
            }
        }
        close(OUT);
    } elsif ($type eq "export_reserve") {
        my $url = "http://cms.blog.livedoor.com/cms/import/mt/export_reserve";
        my $req = new HTTP::Request GET => $url;
        my $res = $ua->request($req);
        if (! $res->is_success) {
            print STDERR "fail to reserve export";
            exit 1;
        }
    } elsif ($type eq "export") {
        my $file = shift;
        open(OUT, ">$file") || die;
        my $url = "http://cms.blog.livedoor.com/cms/import/mt/export";
        my $req = new HTTP::Request GET => $url;
        my $res = $ua->request($req);
        print OUT $res->content;
        close(OUT);
    } else {
        &help;
    }
}
exit 0;

sub help {
    print STDERR "Usage livedoor <blog> <opt>...\nblog: ",
    join("\n      ", keys %blogs), "\n",
    'opt:  date YYYY-MM
      date YYYY-MM-DD
      css <file>
      index_tmpl <file>
      article_tmpl <file>
      category_tmpl <file>
      monthly_tmpl <file>
      raw_log <n> <file>
      export_reserve
      export <file>
';
    exit 1;
}
Filed under: プログラミングと開発環境 — hiroaki_sengoku @ 08:45
2006年4月30日

勤労の義務

「勤労の義務」は、 言うまでもなく日本国憲法第27条に規定されている、 日本国民の3大義務の一つであるが、 そのそろこの勤労に対する固定観念をかえていくべきなのではないか? 来るべき共産社会へのビジョンは、 Web2.0 で初めて見えてきたわけでは決してなく、 何十年も前から様々な形で繰り返し描かれてきた。 例えば、

断絶への航海 ハヤカワ文庫 SF (586)
ジェイムズ・P・ホーガン (著), 小隅 黎

などの近未来小説などの形でも。 SF においては、StarTrek を引き合いに出すまでも無く、 未来には貨幣経済が存在しないという設定のほうが、 むしろ普通となっている。

にもかかわらず、いまだに資本主義以前の ナイーブな勤労観がはびこっているのは、 憲法に基づく洗脳教育のたまものなのだろうか?

人力検索はてな から引用:

「年収300万の自分にとって最高に面白い仕事」か「年収1000万の自分にとって最高に面白くない仕事」どちらかを一生しなくてはならないとしたらあなたはどちらを選びますか?その理由は?

この質問者にとっては、面白くても、面白くなくても、 仕事は仕事なのだろう。 そこにはストックという概念すら存在せず、 ただフローを得るための勤労という思い込みに囚われている。

まずバランスシートを義務教育で教えるべきなのだろうか?
それとも、貨幣経済を飛び越して Web2.0 を教えるべきなのだろうか?

Filed under: 技術者の成長 — hiroaki_sengoku @ 11:19
2006年4月30日

livedoor blog 生ログ取得スクリプト (1)

すでに誰かが絶対に書いているはずとは思ったのですが、 探すよりも書いた方が早そうだったので、 livedoor ブログの生ログを取得する perl スクリプトを 書いてみました。 ついでに、デザインをカスタマイズしたときの、 スタイルシート(CSS)やHTMLのテンプレートも取得できます。 例えば、

livedoor.pl gcd raw_log 10 log.csv

などと実行すれば、10 ページ分 (200行) の生ログを、 CSV 形式でファイル「log.csv」に保存できます。 また、

livedoor.pl gcd index_tmpl index.html

などと実行すれば、インデックスページのHTMLテンプレートを、 ファイル「index.html」に保存できます。 第一引数「gcd」の部分には、 ブログのアカウント名 (スクリプトの先頭部分で定義しています) を 指定してください。 私の場合、 「GCD 日記」と 「仙石浩明CTO の日記」の 二つのブログアカウントがあるので、 それぞれ「gcd」と「klab」という名前で定義しています。

余談ですが、二つのブログを書いているのは、 個人用と会社用とを区別しようというわけではありません。 もともと私のなかでは趣味と仕事の境界線が曖昧なので、 個人と会社でブログを区別しようとしても混ざってしまうでしょうから、 区別することに意味があるとは思えません。 じゃ、なぜ二つのブログなのかと言えば、 「GCD 日記」のほうが よりメモ的でネタを蓄えておき、ある程度考えがまとまったものを 「仙石浩明CTO の日記」へ 書こう、というのが そもそもの意図でした。

やっつけ仕事なので、突っ込みどころ満載(^^;) のスクリプトだとは思いますが、 livedoorブログをお使いの方はご利用頂ければ幸いです。 もちろん、ご利用の際は先頭部分のユーザID & パスワード等を 適宜修正してください。 また、スクリプト中で日本語を使っているので、 このスクリプトは EUC-JP で保存する必要があります。

livedoor.pl (livedoor blog 生ログ & CSS/テンプレート 取得スクリプト)

#!/usr/bin/perl
use LWP::UserAgent;
use HTTP::Request::Common;
use CGI qw/unescapeHTML/;

%blogs = (
    "gcd" => {
        "User"   => "hiroaki_sengoku",
        "Pass"   => "xxxxxxxx",
        "BlogID" => 1600549,
        "ID"     => 11111,
        "PageID" => 22222,
    },
    "klab" => {
        "User"   => "klab_sengoku",
        "Pass"   => "yyyyyyyy",
        "BlogID" => 1631449,
        "ID"     => 33333,
        "PageID" => 44444,
    },
);

&help unless $_ = shift;
if (my $blog = $blogs{$_}) {
    $User =   $$blog{"User"};
    $Pass =   $$blog{"Pass"};
    $BlogID = $$blog{"BlogID"};
    $ID =     $$blog{"ID"};
    $PageID = $$blog{"PageID"};
} else {
    &help;
}

$ua = new LWP::UserAgent;
$ua->agent("Mozilla/5.0 (Windows; U; Windows NT 5.1; ja)");
$ua->env_proxy();
$ua->cookie_jar( {} );
my $res = $ua->request(POST "http://member.livedoor.com/login/index",
                       [ "livedoor_id" => $User, "PASSWORD" => $Pass,
                         ".next" => "", ".sv" => "" ]);
while (my $type = shift) {
    if ($type eq "css" || $type eq "index_tmpl" || $type eq "article_tmpl" ||
        $type eq "category_tmpl" || $type eq "monthly_tmpl") {
        my $file = shift;
        open(OUT, ">$file") || die;
        my $url = "http://cms.blog.livedoor.com/cms/design/edit"
            . "?tmpl=$type&blog_id=$BlogID&id=$ID";
        my $req = new HTTP::Request GET => $url;
        my $res = $ua->request($req);
        if ($res->content =~
            /\<textarea .*name=\"content\" [^\>]*\>([^\<]+)\<\/textarea\>/) {
            my $content = unescapeHTML($1);
            $content =~ s/\r\n/\n/g;
            print OUT $content, "\n";
        }
        close(OUT);
    } elsif ($type eq "raw_log") {
        my $npage = shift;
        ($npage =~ m/^\d+$/ && $npage >= 1) || &help;
        my $file = shift;
        open(OUT, ">$file") || die;
        my $url = "http://analyzer.livedoor.com/log/raw?page_id=$PageID";
        my $prepat = '\<td\b[^\>]*\>\<strong\>\<small\>';
        my $postpat = '\<\/small\>\<\/strong\>\<\/td\b[^\>]*\>';
        for (my $i=1; $i <= $npage; $i++) {
            my $req = new HTTP::Request GET => "$url&p=$i";
            my $res = $ua->request($req);
            my $datepat = '\d\d\d\d\-\d\d\-\d\d \d\d\:\d\d\:\d\d';
            my $date;
            for (split(/(\<small\>$datepat\<\/small\>)/, $res->content)) {
                if (/^\<small\>($datepat)\<\/small\>$/) {
                    $date = $1;
                } elsif (/^\<\/th\>\s*\<\/tr\>\s*/) {
                    my @record;
                    for (split(/\<\/tr\>\s*/, $')) {
                        my $column;
                        if (/$prepat(.*)$postpat/o) {
                            if ($1 eq 'URL') {
                                $column = 0;
                            } elsif ($1 eq 'リファラ') {
                                $column = 1;
                            } elsif ($1 eq 'ブラウザ') {
                                $column = 2;
                            } elsif ($1 eq 'リモートホスト') {
                                $column = 3;
                            } else {
                                die "Unknown column: $_\n";
                            }
                        }
                        if (/\<td\b[^\>]*\>\<small\>(.*)\<\/small\>\<\/td\b[^\>]*\>/){
                            $_ = $1;
                            s/\<\/?a\b[^\>]*\>//g;
                            if (/,/) {
                                s/\"/\"\"/g;
                                $_ = "\"$_\"";
                            }
                            $record[$column] = $_;
                        } elsif (/^\<\/table\>/) {
                            last;
                        } elsif (! /^\<tr\>\s*\<th\b[^\>]*\>/) {
                            die "Unknown format: $_\n";
                        }
                    }
                    print OUT $date, ",", join(',', @record), "\r\n";
                }
            }
        }
        close(OUT);
    } else {
        &help;
    }
}
exit 0;

sub help {
    print "Usage livedoor <blog> <opt>...\nblog: ",
    join("\n      ", keys %blogs), "\n",
    'opt:  css <file>
      index_tmpl <file>
      article_tmpl <file>
      category_tmpl <file>
      monthly_tmpl <file>
      raw_log <n> <file>
';
    exit 1;
}
Filed under: プログラミングと開発環境 — hiroaki_sengoku @ 09:07
« Newer PostsOlder Posts »