Perlの最近のブログ記事

そうそう。先週の月曜日はもう一つ悲しいことがあった。

それは、10年も前に作った「Apache のアクセスログをあれこれ集計する Perl プログラム」のバグが発見されたこと。(^^;

今年の 1月分から集計がされてないというお客さんからの一報で調べてみると、ありゃりゃ、substr で「4桁の年の後ろ 2桁」を抜いているのだが、こりゃ、後ろ 1桁しか抜いてないよ。とほほ・・・。

2010年の'0'しか取らずに(その後前ゼロ補完してるので)'00'年(2000年)のログを見にいってる。今までは、例えば昨年なら'09'と取ってるつもりがやっぱり'9'しか取って無くても、その後 2桁に補完する処理を入れていたので、無事'09'年(2009年)のログに行き着いていたというわけだ。だから10年間もバグが発見されることがなかったんだな(^^;;;
テストはしたんだろうけど、2010年のデータまではやってなかったんだろうな。

まあ、今なら substr なんか使わずに正規表現でビシっと抜くんで、こんな失敗はないんだけど。

日付を見たら、2000年3月に「俺が」作ったプログラムだった。(^^;
ちっ、後輩が作ったプログラムだったら散々文句を言ってやろうと思ったのに!

しかし、このプログラムを作った時は、よもや移り変わりの激しい Web の世界で、10年も使い続けられるとは思ってもいなかったよ・・・(^^;

ありり?

UNION で結合する SELECT 文では ORDER BY って使えんのやったっけ?
エラーにはならないのだが、まったく無視されてるな・・・(^^;
もちろん、SELECT 文単体や、UNION した表全体に対する ORDER BY は問題なく使えるのだが。

UNION の場合、重複行を取るためのソートが表全体に対して行われるわけで、それで各表の ORDER BY は無視されるのかと思いきや、UNION ALL でも一緒やね。

はは。日頃 UNION 使わないもんで(^^;、細かい仕様知らなんだ。

A 003
A 001
A 002
B 102
B 101
C 310
C 320
...

みたいなテーブルから抽出したレコードを

B 101
B 102
A 001
A 002
A 003
C 310
C 320

みたいに並べたいんで(最初の項目の名前をd1、2番目の項目をd2とした場合)、

(SELECT * FROM table1 WHERE d1='B' ORDER BY d2)
UNION
(SELECT * FROM table1 WHERE d1='A' ORDER BY d2)
UNION
(SELECT * FROM table1 WHERE d1='C' ORDER BY d2)

としてたんだけど、

B 102
B 101
A 003
A 001
A 002
C 310
C 320

としか出んね。(テーブルに格納されている順番そのままに出てるだけ)

取りあえずソースが冗長化して格好悪いんだけど、来週にはテストをしたいんで、それぞれ個別に SELECT 実行してプログラム内で結合するようにするわ。

識者の方、良い SQL 文をご存じであればご教示ください。

最近 MySQL では UTF-8 しか扱ってなかったのだが、今回作成したプログラムは「Shift_JIS で書かれた CSV ファイルを読み込んで DB に登録。もちろん NEC 特殊文字や NEC 選定 IBM 拡張文字もガンガン混ざってますぜ!」という香ばしいもの。

ソース自体は EUC-JP で書いたので、DB の中身も EUC-JP で保管するようにして、自前のコード変換関数で Shift_JIS→EUC-JP に変換している。
CREATE TABLE するときにも、ちゃんと DEFAULT CHARSET=ujis 付けてるしね。

なのに、データを登録した後で phpMyAdmin でチェックすると、日本語化け化けやん。

どうも、MySQL ではサーバ側で勝手に文字コードのエンコードをしてしまうので、UTF-8 以外のコードではこういう文字化けが発生してしまうという。
日頃 PostgreSQL ばかり使ってて、いきなり MySQL に来たら絶対ハマっちゃうよなあ、これは。

で、解決方法としては、本来の SQL を投げる前に、

SET NAMES ujis

という SQL を発行すること。

具体的には、

$query    =<<EndOfQuery;
SET NAMES ujis
EndOfQuery

$sth = $dbh->prepare($query);
$sth->execute();

としてから、本来の SQL を

$query    =<<EndOfQuery;
INSERT
INTO
  hogehoge
~本来の SQL~
EndOfQuery

$sth = $dbh->prepare($query);
$sth->execute();

と実行すれば良いと。

なんだかなぁ(^^; テーブル作る時に DEFAULT CHARSET=ujis なんて宣言している意味がなかったな。(^^;
まあ、DB に詳しい偉い人たちが文句を言ってないってことは、この実装も間違いではないんだろうけど、なんだかなあ(^^;

ちなみに、MySQL の設定ファイルや実行時のオプションであれこれする方法もあるようなのだが、今回使用しているのが安いレンタルサーバで、MySQL の設定ファイルは触れない仕様らしいので、毎度 SET NAMES ujis を発行する方法しかないようだ。

とほほ。またも無駄な時間を・・・

psql -n hogedb とコマンド叩いて psql(PostgreSQL のターミナル型フロントエンド)を立ち上げ、

INSERT
INTO
    t_address
VALUES (
    '1',
    '742-0301',
    '岩国市周東町祖生',
    '2010-01-08 17:40:46'::timestamp
);

という SQL を実行しても、SELECT してみると、

hogedb=# SELECT * FROM t_address 
 uid |   post   |          address           |        cdate
-----+----------+----------------------------+------------------------
 1   | 742-0301 |                            | 2010-01-08 17:40:46
(1 rows)

という具合に住所が表示されない。
エラーは出てないので、登録されているはずだがなあ・・・

DB の Encoding は EUC_JP。

PuTTY の文字コードの設定が UTF-8/Auto-Detect Japanese になっていたので、EUC-JP に変えてみたが駄目。

Perl でちょいプロを作って、DBI/DBD 経由で書き込んでみたが同じ状況。エラーは出ないのだが、psql で該当テーブルを見てみると、日本語が表示されない。

今度は、SELECT する「ちょいプロ」作って確認してみよ・・・とプログラムを書きだしたところで、ハっと思いついて pg_dump hogedb > hogedb.txt と DB 内容のダンプを出力してみた。

そしたら、

--
-- Data for Name: t_address; Type: TABLE DATA; Schema: public; Owner: xxxxxx
--

COPY t_address (uid, post, address, cdate) FROM stdin;
1       742-0301        岩国市周東町祖生      2010-01-08 17:40:46

という具合にしっかり日本語部分も登録されているのが確認できた。

psql が日本語を表示してないだけか!?
う~ん、何でじゃろうね。
psql って、ページャーは more を使ってるんだったっけ?

なにせ、PostgreSQL 8.4.2 と CentOS 5.4 の組み合わせが初めてなのと、ここ数年、そういやあ新規開発案件は MySQL ばかりで、psql を使ってあれこれというケースもあまりなかったよ。
だから知らないのだが、最近の psql はこうなのか?

昔、psql が日本語対応してなかった時も、全然表示しないということはなかったからなあ。文字化けするだけで。

ま、取りあえず psql 使ってする作業は終わったので、これ以上原因は追及しないが、またも無駄な時間を過ごしてしまった。(^^;

いやあ、Web 上の某情報を自動で取ってきて DB 化するという処理を、やっつけでパパッと Perl スクリプトで書いて実行してたら、

DBD::mysql::st execute failed: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ...

なエラーが。

はい。シングルクォーテーションのエスケープ漏れです。(^^;
今回、ベタで SQL 書いて、直接 DBD/DBI で execute してるからねえ。ちゃんと手動でエスケープしないと。

INSERT 文にセットする前に、

$value =~ s/\'/\'\'/g;

とかね。(シングルクォーテーションを二重に)

そしたら今度は、別のスクリプトで、

sh: -c: line 0: unexpected EOF while looking for matching `''
sh: -c: line 1: syntax error: unexpected end of file

なエラーが・・・とほほ。

シングルクォーテーションを含んだ文言を echo してプログラムに食わせる処理を Perl 内で実行してたんで、そこで引っかかってる。(^^;

ちゃんと、

$value =~ s/\'/\\\'/g;

しなくちゃいけませんなあ。(シングルクォーテーションの前に \ を)

ちゃんと作る時は、この辺の処理は自前の関数で処理するようにしてるんで、ちょっとテスト的にやっつけでチャチャっと作るとついつい忘れてしまいますなあ。

と、自責の意味で書いておく。

さて、Perl の sprintf で、文字列の右側に 0 を補完するのはどうすればええんじゃろうねえ?

例えば、

$d    = '112200225';

して、この値を sprintf で編集すると、

$d      = sprintf("%020d", $d);

なら、00000000000112200225 となっちゃうんだけど、実際は、後ろに 0 を補完して、11220022500000000000 としたいのよ。

試しに sprintf("%-020d", $d) ってやってみたけど、これじゃあ何も補完されないだけだった。

後ろにスペースを埋めるというやり方はあるので、先にスペースを埋めてからそれを正規表現で 0 に変換しちゃえばいいんだが、こういうやり方しかないんかいのお?

$d    = '112200225';
$d    = sprintf("%-20s", $d);
$d    =~ s/\s/0/g;
print "$d\n";

とすれば、出力結果は 11220022500000000000 だ。
これをね、sprintf 一発でやりたいんだけどなあ。

識者の方、ぜひご教示ください。

色々事情があって、OpenPNE の「メッセージを送る」画面でメッセージ送信をすると、OpenPNE のユーザのメールアドレスに直接メールを送信する機能を Perl で実装した。

OpenPNE ではメールアドレスは暗号化されているので、OpenPNE のソースを元に、以下のような復号化スクリプトを書いて復号を行った。

#!/usr/local/bin/php
<?php
    $address = $argv[1];
    define('ENCRYPT_KEY', 'hogehoge_key');
    $path ="/www/lib/include";
    set_include_path(get_include_path() . PATH_SEPARATOR . $path);
    include_once 'Crypt/Blowfish.php';
    $blowfish = new Crypt_Blowfish(ENCRYPT_KEY);
    $address = base64_decode($address);
    $address = $blowfish->decrypt($address);
    echo $address;
?>

で、不思議なことに、hogehoge3@exsample.co.jp宛のメールアドレスにはメールが届くのに、hoge@exsample.co.jp宛には届かない。
どちらも、ちゃんと存在しているメールアドレスなのにだ。

原因は、復号されたメールアドレスにあった。
どうも、上記のスクリプトで復号すると、24バイト以下のメールアドレスの後ろに 0x00 のコード(NULL)がセットされてた・・・

一応、後ろにスペースがセットされてたらやだなと思って、Perl 側で、

$address =~ s/\s//g;

な置換はかけてたんだけど、NULL値は \s じゃ引っかからんのね・・・

ということで、

$address =~ s/[\x00]//g;

として不要な NULL値を削ってやったら、問題なくメールが届くようになった。

ああ、ほんまに、ラピッドサイトサーバは maillog が見れないんで駄目駄目サーバだ。
独自のシステムを動かしたい企業は、月数千円の金をケチるのはやめて、WebARENA とかを使うべきである。(・・・て話は、この間もしたな(^^;)

某ホスティングサイト上でシステムの開発をしているんだが、そのサーバには許せないことに、Perl モジュールの Jcode.pm が入っていない。

root 権限もくれないので、普通に make 出来ないし。
なので、展開したファイルを手動でコピーして使ってたんだが、それだと UTF-8 の変換で問題があるんだよねえ。

その辺の原因を追及する時間がもったいなかったので、IN/OUT のデータも、Perl のソースも全て UTF-8 にして、変換が必要ないようにしてたんだけど、どうしてもメール送信しなくちゃいけなくなって。
メールも Unicode のままでも良いんだけど、俺の使ってる AL-Mail が Unicode のメールが読めないんでねえ。(笑)

ということで、Jcode.pm を使って変換することにしたんだけど、いやぁ、ハマった、ハマった。(^^;

変換時にエラーが出る対応で、Jcode/_Classic.pm を修正しちゃったもんだから(「Unicode 変換時の Jcode のエラー」に書いたように、これは誤った対処方法)、その後、エラーが出ないのに、何で正しく UTF-8 がらみの変換が出来ないのか悩むことに・・・(^^;

これで、丸一日無駄にしちゃった。(^^;

最後には、nkf 使って、

    my $str    = join '', @_;
    my $tmp    = (time) . $$ . '.txt';
    open(NKF, ">$tmp");
    print NKF $str;
    close(NKF);
    $str    = `cat $tmp|/usr/local/bin/nkf -j`;
    unlink $tmp;
    return($str);

みたいな関数作って対応してたんだけど、さすがに美しくないので・・・(^^;
結局、ユーザ環境に Jcode.pm を make install することにした。

% perl Makefile.PL INSTALLDIRS=site INSTALLSITELIB=/usr/home/hoge/www/cgi-bin
% make
% make install

でOK。
make install の時に、(一般ユーザの書込権限の無いディレクトリに書き込もうとするので)perllocal.pod のインストールで失敗するが、Jcode.pm の実行には影響無い。
もちろん、perllocal.pod をユーザ環境にインストールするよう設定出来るが、ま、インストールしなくても困んないので、今回はこれでヨシ。

これで、Jcode/Unicode.pm が作られるので、「Unicode 変換時の Jcode のエラー」に書いたように、Jcode/Unicode/NoXS.pm を修正する必要は無い。

この時に、必ず @INC に含まれるディレクトリをインストール先にすること。(でないと、Perl スクリプトの頭で、いちいちインストール先を @INC に加える処理が必要になるけえ)
なので、俺は、cgi-bin の直下にインストールした。(. ディレクトリは @INC に含まれているからね)

ああ。root 権限くれるホスティングサービスだったら、こんな苦労しなくていいのにな。
Perl で作った独自システムを動かしたい会社は、月々数千円の金を惜しまず、WebARENA とか使えばええのだ。いや、マジで。
う~む、何か、Jcode.pm を使って、EUC から UTF-8 に変換しようとすると、

Undefined subroutine &Jcode::_Classic::euc_utf8 called at lib/Jcode/_Classic.pm line 255.

てなエラーが出てしまう。

サイトによっては「Perl のバージョンが古いせい」とか訳の分からない説明をしてるところもあるけど、実際にはそんな単純な話ではない。

この原因は、ちゃんと Jcode.pm のインストール時に make; make install をして Unicode.pm を作成していないから。
なので、本来なら make して、Unicode.pm を作ってやるのが一番だが、ホスティング環境によっては SSH 接続等でのシェル使用を許してなくて(FTP のみ可とかで) make 出来ないケースもあるしな。

解決策として、

Jcode/_Classic.pm の 255行目付近を、

sub utf8{
    load_module("Jcode::Unicode");
    euc_utf8(${$_[0]->[0]});
}
    ↓
sub utf8{
    load_module("Jcode::Unicode");
    Jcode::euc_utf8(${$_[0]->[0]});
}

と変更し、Jcode::Unicode(Unocode.pm)の euc_utf8 ではなく、Unicode/NoXS.pm 内の Jcode::euc_utf8 を呼ぶようにしてやると書いているサイトもあるが嘘。
これだと、確かにエラーで落ちることはなくなるが、UTF-8 がらみの変換が正しく行われない。

UTF-8 がらみの変換、

$str    = Jcode->new($str, 'utf8')->h2z->jis;

とか

$str    = Jcode->new($str, 'utf8')->h2z->euc;

が正しく行われるようにするには、Jcode/Unicode/NoXS.pm を以下のように編集する。

% diff NoXS.pm NoXS.pm_org
56c56
< sub Jcode::_Classic::ucs2_euc{
---
> sub Jcode::ucs2_euc{
71c71
< sub Jcode::_Classic::euc_ucs2{
---
> sub Jcode::euc_ucs2{
87c87
< sub Jcode::_Classic::euc_utf8{
---
> sub Jcode::euc_utf8{
90,91c90,91
<     &Jcode::_Classic::euc_ucs2($r_str);
<     &Jcode::_Classic::ucs2_utf8($r_str);
---
>     &Jcode::euc_ucs2($r_str);
>     &Jcode::ucs2_utf8($r_str);
94c94
< sub Jcode::_Classic::utf8_euc{
---
> sub Jcode::utf8_euc{
97,98c97,98
<     &Jcode::_Classic::utf8_ucs2($r_str);
<     &Jcode::_Classic::ucs2_euc($r_str);
---
>     &Jcode::utf8_ucs2($r_str);
>     &Jcode::ucs2_euc($r_str);
101c101
< sub Jcode::_Classic::ucs2_utf8{
---
> sub Jcode::ucs2_utf8{
124c124
< sub Jcode::_Classic::utf8_ucs2{
---
> sub Jcode::utf8_ucs2{

これでバッチリ!
Jcode/_Classic.pm の編集はしちゃあいかんぜ。
rtf_test.jpg
現在提案中の Webシステムの話。

入力した内容を反映させた書類を出力する必要があったので、PDF ファイルを出力する仕様で見積もっていたのだが、MS-Word 用の ファイルを出力してほしいとの要望あり。
Perl での開発を考えていたので、RTF::Writer モジュールを入れて RTF(Rich Text Format)ファイル出力のテストをしてみた。

RTF::Writer モジュールは、CPAN モジュールを使ったインストールで一発OK。
何か、CPAN モジュールできれいにインストール出来る Perl モジュールに出会ったのが久しぶりなので妙に嬉しい。(笑)何かと Test で失敗する事が多いからなあ。(^^;

で、早速、Writer.pod に載っているサンプルを元に、日本語がうまく扱えるかテスト用のスクリプトを書いてみた。

use RTF::Writer;

my $rtf = RTF::Writer->new_to_file("test.rtf");
$rtf->prolog('title' => "テスト帳票", 'fonts' => ["MS Pゴシック"]);
$rtf->number_pages;
$rtf->paragraph(
  \'\fs40\b\i',  # 20pt, bold, italic
  "これは RTF 出力のテストです"
);
$rtf->close;

実行結果は上の画像のとおり。ちゃんと、RTF フォーマットのファイルが作られて、Windows2000 上の Microsoft Word 2000 で読み込むことが出来た。
RTF ファイルで良いということなら何とかなりそうだ。

ちなみに、日本語の含まれたファイルは Shift_JIS で出力されるようだ。
なので、セットする文字列自体も Shift_JIS でないと文字化けする。今回は、テストということで、スクリプト自体を Shift_JIS で書いて対処した。
本番プログラムを Shift_JIS で書くわけにはいかんので、その時は Jcode とか使って Shift_JIS に変換してやる必要がありますな。

ま、問題無し。

でも、エンドユーザさんとこで、「RTF って何?DOC ファイルじゃなきゃ嫌だ!」とか言い出したら面倒だなあ。(^^;

俺が直接エンドユーザ折衝してたら、思わず「はぁ?今時 DOC ファイルじゃないと駄目って、何?DOC ファイルじゃないと表現できない部分ってありましたっけ?まさか、拡張子が rtf じゃ Word 用のファイルってわからないから?みんなちゃんとした社会人なんだから、一回説明すれば大丈夫でしょ?」くらいの厭みは口走っちゃいそうで怖いけど。(笑)

どうしても DOC でなきゃ嫌だということになれば、Java で Jakarta POI 使ってほげほげか。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちPerlカテゴリに属しているものが含まれています。

前のカテゴリはAIR/Flexです。

次のカテゴリはPHPです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら