愉悦のツール

  1. 存在するデータベースを調べる
  2. sedのメモ
  3. gnuplotでデータベース出力をグラフ化する
  4. テキストログをデータベースへ
  5. PostgreSQL.4.3をインストールする
  6. servletの場所を追加する
  7. 多くのファイルの中をsedで書き換える 3
  8. 特定のアドレス群からのメールを転送する
  9. 多くのファイルの中をsedで書き換える 2
  10. postgreSQLのラージオブジェクトを使って,大量にあるデジカメ画像を整理する
  11. 多くのファイルの中をsedで書き換える

存在するデータベースの一覧を得る

postgresqlの場合

 $ psql --list
 または
 $ psql -l

sedのメモ

先頭の数字を取得する表現

sed "s/^ *[0-9]*/number/" file.txt

IPアドレスに一致する表現

sed "s/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/IP Address/" file.txt

12.32 msというマイクロ秒表記から数字のみ取得

sed "s/\([0-9\.]*\) ms/\1/" file.txt

*を-1に置き換える

sed "s/\*/-1/" file.txt

!を発見したらflagをあげる

sed "s/\!/flag/" file.txt

全部組み合わせて,tracerouteの出力をSQLに変換する

traceroute.shさすがに一行に収まらない,おまけに組合せが多いので,awkで書き換えることを検討.


gnuplotでデータベース出力をグラフ化する

gnuplotで図を書きやすいように,データベースの出力を得る.

利用するテーブル

 CREATE TABLE logintime
 num char(7)
 date timpstamp
 title text

番号毎のログイン回数を得る

sfjt=# SELECT num, COUNT (*) FROM logintime GROUP BY num ORDER BY num;

現実には,誤った番号を入力するのもログに残っているので,正規の番号リストに一致するもののみを得るように,条件を追加する.

sjft=# SELECT logintime.num, COUNT (*) FROM logintime,userinfo WHERE in userinfo.num GROUP BY logintime.num ORDER BY logintime.num;

利用の多い順に並び替える

 sfjt=# SELECT logintime.num, COUNT (*) FROM logintime,userinfo WHERE loginti me.num=userinfo.num GROUP BY logintime.num ORDER BY COUNT(*) DESC;

番号毎に週間利用量を表示する

 sfjt=# select count(*) from logintime where num='0000000' AND date>='2004-05 -06' and date<='2004-05-07';

数値を日付に変える

 date(int)

一週間の定義をテーブルに持つ(反則?)

CREATE cdate 
   number int,
   day timestamp

1週間の利用状況を調べる

 sfjt=# SELECT number,COUNT(*) from cdate,logintime WHERE num='0000000' AND date>=

gnuplotでグラフを書く

製作途上


PostgreSQL.4.3をインストールする

必要なもの:Unixが全うに動く機械.gmake (BSD makeではない), 愛情

手近なmirrorからソースを入手する.

ソースを展開する.

 $ ./configure --enable-locale --enable-multibyte=EUC_JP --enable-nsl='ja en' --with-perl --enable-odbc --with-java
 $ ./configure --enable-locale --enable-multibyte=EUC_JP --enable-nsl='ja en' --with-perl --enable-odbc

今回はJavaを利用しないので,下段で対応.

ユーザを登録

 $ groupadd postgres
 $ useradd -g postgres -m -d /home/postgres postgres

ソースを全てpostgresの所有にする(後のテストのために必要).

 $ chown -R postgres.postgres ./

postgresになり,インストール作業の継続

 $ su - postgres
 $ cd /usr/local/src/postgresql-7.4.3
 $ make
 $ make check

ちなみにテスト結果がコレ↓

======================
 All 93 tests passed. 
======================

7.4.2とテスト項目数は変わらず.

rootに戻って

 $ make install
path変更を忘れずに

  LD_LIBRARY_PATH=/usr/local/pgsql/lib
  export LD_LIBRARY_PATH

/sbin/ldconfig /usr/local/pgsql/lib

postgreユーザにて,データベースを初期化する

$ /usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data

以下のような出力が得られる.

Success. You can now start the database server using:

    /usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data
or
    /usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data -l logfile start

起動スクリプトのサンプルは,src/contrib/start-script以下.

NetBSDの場合には,/usr/local/etc/rc.d/postgresqlとして,中身を自分の環境に合わせて変更.

postgresユーザで利用者を登録

$ createuser hogera

自分自身のデータベースを構築.

createdb --encoding=EUC_JP sfjt

テキストログをデータベースへ

traceroute

ネットワークに不調がしばしば見受けられる仕事場なので,定常状態の観測のために,tracerouteで記録を取ることにした.統計的な処理をするために,データベースに格納しておくこととする.

テーブル設計

CREATE table traceroute (
    identify SERIAL PRIMARY KEY,
    date timestamp DEFAULT CURRENT_TIMESTAMP,
    flag boolean DEFAULT false,

    hostname text,                               -- destination
    execute_time timestamp,                      -- cron date
    sequence int,                                -- routing order
    router char(16),                             -- IPv4
    ms1 int,                                     -- if *, set -1 
    ms2 int,                                     --
    ms3 int);                                    --

tracerouteから出る出力のパタン,man tracerouteより

 6  128.32.197.4 (128.32.197.4)  40 ms  59 ms  59 ms
 7  131.119.2.5 (131.119.2.5)  59 ms  59 ms  59 ms
 8  129.140.70.13 (129.140.70.13)  99 ms  99 ms  80 ms
 9  129.140.71.6 (129.140.71.6)  139 ms  239 ms  319 ms
10  129.140.81.7 (129.140.81.7)  220 ms  199 ms  199 ms
11  nic.merit.edu (35.1.1.48)  239 ms  239 ms  239 ms
12  * * *
13  128.121.54.72 (128.121.54.72)  259 ms  499 ms  279 ms
14  * * *
15  * * *
16  * * *
17  * * *
18  ALLSPICE.LCS.MIT.EDU (18.26.0.115)  339 ms  279 ms  279 ms
 7  * * *
 8  * * *
 9  * * *
10  * * *
11  * * *
12  * * *
13  rip.Berkeley.EDU (128.32.131.22)  59 ms !  39 ms !  39 ms !

経験上,以下のような行もありえる

8  hoge.hoge.hoge (192.168.0.1) 34 ms  *  340 ms

cronで起動するコマンド

作成中

syslog系

/etc/syslog.confをかきかえて,データを送るようにする.

apache httpdの場合

/usr/local/src/postgresql-7.4.3/contrib/tips/README.apachelogを参考にした.

エラーログにこんなのが残っていたら,エラーを直そう.

unable to start piped log program ' su -c "sed \"s/, - );$/, -1 );/\" | /usr/local/pgsql/bin/psql www_log" nobody': No such file or directory
Unable to open logs

nobodyにはパスが通っていないかったので,/bin/su, /bin/sedとする.

つぎに出現したエラー

piped log program ' /bin/su -c "/bin/sed \"s/, - );$/, -1 );/\" | /usr/local/pgsql/bin/psql www_log" nobody' failed unexpectedly
/bin/su: invalid option -- 1

suに1が渡されるのは,suの仕様の違いによるらしい情報源

TransferLog '|/bin/su nobody -c "/bin/sed \'s/, - );$/, -1 );/\' | /usr/local/pgsql/bin/psql www_log nobody"'

また,こちらテーブル定義を参考にして,ログをとりまくることに決定.

覚え書き,TransferLogは引数を一つしか取れない.


servletの場所を追加する

tomcat5/conf/Catalina/localhost/追加.xml

workers2.propertiesに追加

web.xmlにservletを追加

HelloWorldなど,確実に動くサーブレットで検査


多くのファイルの中をsedで書き換える 3

ローカルのスタイルシートで管理する方針に変えた.

sed "s/\"\/2003\/style.css\"/\"\.\/style.css\"/" *.jsp

バックアップを取る

tar cvfz backup.taz *.jsp

気力でいきなりスクリプトをコマンドラインより入力する.

コマンドプロンプトを'$'で示している.

$ for file in `ls *.jsp`
> do
> sed "s/\"\/2003\/style.css\"/\"\.\/style.css\"/" $file > file.new
> mv $file $file.bak
> mv $file.new $file
> done
>はシェルより,まだコマンド入力が完成していないことを示す,プロンプトである.

特定のアドレス群からのメールを転送する

たまたまある一致があるので,ちと焦る.しかも,もっと簡単な方法があった.

転送したいアドレスが集団である場合の処理を実現してみよう

qmail-commandconredirectを読む.

# まずローカルに保存
./Maildir/

#そして転送
| condredirect sfjt@hogehoge -c /home/sfjt/usr/bin/forward.sh

-cをつけないと環境変数が受け渡されずにエラーをlogに吐く.

肝心のコードの方は,

#!/bin/sh

WHITE="/home/sfjt/usr/bin/whiteAddress.txt"

for address in `cat $WHITE'
do
        if [ $SENDER = $address ]
               then
               exit 0
        fi
done
exit 100

環境変数の受け渡しに注意.

mew使い

だったら,Mail/inboxとか,Mail/friendsとか,転送したいメールアドレスのあるディレクトリで,

grep "From: .* <.*>" * |sed "s/.* <\(.*@.*\)>.*/\1/" | sort |uniq >> ~/filename

などとアドレスを回収し,再度sort,uniqをかけて,目で点検する.添付ファイルなど,妙なものからアドレスを拾ってくる場合がある.

こっちが,以前にperlで書いたのが以下のメール分解転送スクリプト.上の方法を知っていれば,もっと早かったかもしれない.


#!/usr/bin/perl

# jcode.plのパス
require '/home/sfjt/usr/bin/jcode.pl';
use Net::SMTP;# Net::SMTPモジュールを使用

##----------------------------------------------------
# Debug and Development 
# Set 1 to debug mode, and others to utility mode

    $debug=0;

if ($debug==1) {
    $specialAddress = 'sfjt@hogehoge';
    $mail_to = 'sfjt@gerogero';
} else {
    $specialAddress = 'president@hohoho';
    $mail_to = 'president@hehehe';
}
##---------------------------------------------------

$mail_server = 'SMTP';
$mail_from = 'sfjt@kerokero';
$mail_subj = "転送";
$mail_body = "";

# ------------ ------------ ------------ ------------
    $flag=0;
$header_flag=0;
while (<>) {

    if ($_ =~ /^\n/) {
	$header_flag=1;
    }

    if ($header_flag==1) {
	$mail_body .= $_;
    } else {
	if ($_ =~ /^Subject:*(.*)/) {
	    $mail_subj = $1;
	}
	if ($_ =~ /^From:.*$specialAddress.*/) {
	    $mail_from=$specialAddress;
	    $flag=1;
	}
    }
}
if( $flag ) {
    &send();
    exit 0;
}
exit 0;

# ------------ ------------ ------------ ------------ ------------ 

sub send {
    &sendmail($mail_server, $mail_from, $mail_to, $mail_subj, $mail_body);
}

#--------------------------------------
# メール送信
#--------------------------------------
sub sendmail {
    (my $SMTPserver,my $from,my $to,local $subj,local $body) = @_;

    my $SMTP;
    
    &jcode'convert(*subj,'jis');
    &jcode'convert(*body,'jis');

# オブジェクトの作成
    $SMTP = Net::SMTP -> new($SMTPserver,# SMTPサーバー名を指定
			 Hello => $SMTPserver,# SMTPドメイン名を指定
			 Timeout => 60);# 接続待ち許容時間(秒)
			 #ヘッダ部の組み立て
			     $SMTP -> mail($from);# 送信元メールアドレスを指定
			$SMTP -> to($to);# 宛先メールアドレスを指定
# CC(カーボンコピー)先メールアドレスを指定
# $SMTP -> cc($cc);
# ブラインドCC先メールアドレスを指定(送信元にもBccでメールを送る)Bcc不要時、コメント行にする。
# $SMTP -> bcc($from);
#データ部の組み立て
    $SMTP -> data();
    $SMTP -> datasend("From:$from\n");# 送信元(データ部)
	$SMTP -> datasend("To:$to\n");# 宛先(データ部)
	    $SMTP -> datasend("Subject:$subj\n");# 件名
		$SMTP -> datasend("\n");
    $SMTP -> datasend("$body\n");# 本文
	$SMTP -> dataend();# データ終端、メール送信
	    $SMTP -> quit;# SMTP接続の終了
	    }

多くのファイルの中をsedで書き換える 2

あー,スタイルファイルを導入するのを忘れたファイルがいくつも,ある.

さぁ早速sedで書き換えよう.

<HEAD><TITLE>hoge</TITLE></HEAD>

という,大文字なところがアレな部分を見逃すとしても,スタイルシートを読み込む部分を作らねばならない.

<HEAD>
<link rel="stylesheet" href="style.css">
<TITLE>hoge</TITLE>
</HEAD>

という出力を目指そう.

部品を作る

タグは小文字にしたいので,少々方針変更.

製作途上

sed "s/<HEAD><TITLE>\(.*\)</TITLE></HEAD>/<head> ←ここは改行する
   <link rel=\"stylesheet\" href=\"style.css\">
   <title>\1</title>" input > output

postgreSQLのラージオブジェクトを使って,大量にあるデジカメ画像を整理する

ラージオブジェクトを含むテーブルを定義する

ラージオブジェクトの定義は簡単

CREATE TABLE images (
        identify SERIAL PRIMARY KEY,              -- シリアル番号
	date timestamp DEFAULT CURRENT_TIMESTAMP, -- 作成日
	flag boolean DEFAULT false,               -- 削除したいときに利用

	fileoid OID,                              -- ラージオブジェクト
	filename text,                            -- ファイルネーム(2K-byte max)
	basename char(256),                       -- ベースネーム(ext2による)
	mimetype char(64),                        -- MIME-type(推定)
	filesize int);                            -- ファイルサイズ
char(N)とすると,カラムでそれを強制サイズで表示させらるのが格好わるい. SQL文字関数によると,rtrim()として,右空白を削除するのが良いようだ.
select rtrim(basename) from images limit 1;
  rtrim  
---------
 001.jpg
(1 row)
として,無事に動作確認.固定長というのは,そういうものだ,という話.

fileoidというカラム名で,ラージオブジェクトを利用するようにしている.また,identify, date, flagは管理用にいつも付けている.

いまのところ,ラージオブジェクトのOIDは,バックアップを取る,リストアすると変更されてしまう仕様なので,OIDを検索などの操作には利用できないのである.

ファイルそのものの保管のためには,上記程度の情報で十分であろう.

ファイルからデータを読み込むスクリプト

perlで全部処理する

小さいコマンドを組み合わせて,処理をするのが,unixの使いかたとは言いながら,今回のように,対象とするファイル数が非常に膨大であると,予想される場合には,なんらかの言語で処理をすることを考えた方が良い.

シェルスクリプトから毎回起動されるコマンドの実行が負荷となるので,処理時間が多くなることが予想されるからである.

そこで,今回はperlで処理を記述する.

もちろん,Javaでかくのも良いし,CやPHP,tclなどで書くのも良い.

多くの画像ファイルは,階層構造のディレクトリに格納されているであろう.そこで,再帰的にディレクトリを下り,ファイルを対象とする手続きを考える.

function (ディレクトリ名) {
    そのディレクトリの一覧取得;

    for (一覧のそれぞれについて) {
        if (対象がディレクトリ) {
            function(各ディレクトリを処理);
        } else {
            ファイルを処理;
        }
    }
}

perlで実装すると,次のように再帰的に記述することになる.

#!/usr/bin/perl

my $top=@ARGV[0];
$top = '.' if $top eq '';

&show_file($top);

sub show_file {
    local $var=@_[0];
    opendir DIRH, $var or print "dir: $var:$!";
    local @files = readdir DIRH;
    foreach(@files) {
	next if $_ =~ /^\.{1,2}$/;
	local $curfile = $var."/".$_;
	open FILE, $curfile or print "file:$_:$!";
	if (-d FILE) {
	    &show_file($curfile);
	} else {
	    print "F: ",$curfile, "\n"; ## ここで個別のファイルが得られる
	}
	close FILE;
    }
    closedir DIRH;
}

なんか//が2度付く時がある.

perlでpostgresに触れる

perlで使えるpostgresql関数については, perl5拡張を参照されたい.

#!/usr/bin/perl

use Pg;

## データベースへの接続を行う
my $conn = Pg::connectdb("dbname=sfjt");
if (PGRES_CONNECTION_OK ne $conn->status) {
    printf("コネクションエラー");
    exit;
}

ファイルのサイズや,Mime-typeを取得する

CGI.pmを使ったファイルアップロードのサンプルCepheidさんのページ参考に機能を切り出す,つもりが,CGIとして呼び出されていないと,機能が使えないようなので,却下.

ファイルの種類を推測しますを見る.これが使えそうである.

#!/usr/bin/perl

use File::MMagic;

$mm = new File::MMagic;
$res=$mm->checktype_filename(@ARGV[0]);
print $res . "\n";

無事に目的の文字列を取得できたので,コードに埋め込む.

Pgのインストール

Pgが入っていない.おまけに2000年4月から更新されていないので,DBI等を利用する方が良いのかも知れない.

pgsql_perl5-1.90.tar.gzを取得

READMEを読む

$ chown postgres.postgres . *
$ POSTGRES_INCLUDE=/usr/local/pgsql/include
$ POSTGRES_LIB=/usr/local/pgsql/lib
$ export POSTGRES_INCLUDE POSTGRES_LIB
$ perl Makefile.PL
$ make
$ make test
PERL_DL_NONLAZY=1 /usr/bin/perl -Iblib/arch -Iblib/lib -I/usr/lib/perl5/5.6.1/i386-linux -I/usr/lib/perl5/5.6.1 test.pl
Can't call method "errorMessage" on an undefined value at test.pl line 99.
make: *** [test_dynamic] エラー 255

linuxの場合は,/sbin/ldconfig -vとして確認.なければ,/etc/ld.so.confに追加して,ldconfigの再実行.

パッチがでていた.

日本でも同じ例が報告されていた

以下はルートで

 $ make install
75438 89212 75569 70767 89537 65533 89609 73961 67687 58682
削除するのを忘れた,oidのメモ.tableをうっかり消してしまうと,
large objectを後から消せなくなるので,メモしておいた.

ラージオブジェクトを削除する

MLの投稿を確認して,perl命令で削除する.

#!/usr/bin/perl

while  {
     local $loid = $_;
     $ret = $conn->lo_unlink($loid);
     if ($ret == -1) {
          print "$_ はラージオブジェクトではありません\n";
     } else {
          print "$_ を消去しました\n";
     }
}

pgsql7.4.1では上記は動作しなかった.ひょっとして,ガーベッジコレクションが実装された?継続調査.

oidを失ったlargeobjectの復元を見て,生のlargeobjectのidを見たが,既に該当するidはなかった,戻り値が異なっている可能性もある.継続調査.

画像ファイルをpostgresに登録するスクリプト(alpha)

引数なしで起動すると,再帰的にカレントディレクトリ以下を探して,存在する全てのファイルをpostgresqlのラージオブジェクトとして,登録する.

#!/usr/bin/perl
use Pg;
use File::MMagic;

## register_file_to_db.pl

$dbname="sfjt"; ## data baseの名前をセット

my $top=@ARGV[0];
$top = '.' if $top eq '';

#################################################
my $conn = Pg::connectdb("dbname=$dbname");
if (PGRES_CONNECTION_OK ne $conn->status) {
    printf("コネクションエラー");
    exit;
}

&search_file($top);

#################################################
sub search_file {
    local $var=@_[0];
    opendir DIRH, $var or print "dir: $var:$!";
    local @files = readdir DIRH;
    foreach(@files) {
	next if $_ =~ /^\.{1,2}$/;
	local $curfile = $var."/".$_;
	open FILE, $curfile or print "file:$_:$!";
	if (-d FILE) {
	    &search_file($curfile);
	} else {
	    ®ister($curfile,$_); # $_ == base名を送る
	}
	close FILE;
    }
    closedir DIRH;
}

##############################################
sub register {
    local $file = @_[0];
    local $base = @_[1];

    open(objectFILE,$file) || print STDERR "cant open $file:$!\n";
    $size = -s objectFILE;
    close(objectFILE);

    ## mime codeを取得 # 毎回オブジェクトを作るのはコストが高い,直したい.
    local $mm = new File::MMagic;
    $mime= $mm->checktype_filename($file);
    
    $conn->exec("BEGIN");
    $oid=$conn->lo_import($file);
    $conn->exec("END");

    local $qstr="INSERT INTO images";
    $qstr = "$qstr (fileoid,filename,basename,mimetype,filesize)";
    $qstr = "$qstr values('$oid','$file','$base','$mime','$size');";
    $conn->exec($qstr);
#   print $qstr . "\n";
}

ラージオブジェクトの表示

格納するだけでは,役に立たないので,画像を表示する部分を作成する.

ここはJava Servletで作成する/perlのようなインタプリタ言語で作ると,画像ファイルの数だけコマンドが呼び出されることになり,コストが高過ぎる.

pgdoc7.4.2/jdbc-binary-data.htmlを参考にしてプログラムを組む.

サンプルどおりでは上手くいかないのが,世の常ながら,ちといらんコードが多いかも知れない,実現しないとならないことは,

/** How to use

 <img src="http://localhost/servlet/ImgFromDB.java?imgid=123456">

 */

// servlet
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

// SQL
import java.sql.*;
import org.postgresql.largeobject.*;

// Enumeration
import java.util.*;


public class ImgFromDB extends HttpServlet {

    private Connection conn;
    private Statement stmt;
    private LargeObjectManager lobj=null;

    private boolean opendb() {
	String driver = "org.postgresql.Driver";
	String url = "jdbc:postgresql://127.0.0.1:5432/sfjt";
	String user = "sfjt";
	String passwd = "nanika kakeyo";

	try {
	    Class.forName(driver);
	    conn = DriverManager.getConnection(url,user,passwd);
	} catch (ClassNotFoundException cnfe) {
	    cnfe.printStackTrace();
	    return false;
	} catch (SQLException sqle) {
	    sqle.printStackTrace();
	    return false;
	}
	return true;
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	test_db(request,response);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	test_db(request,response);
    }

    private void test_db (HttpServletRequest request, HttpServletResponse response) throws IOException {

	Enumeration paramNames = request.getParameterNames();
	boolean paramP=true;
	String identify="21";// エラー画像として登録したもの
	String sql="";
	ResultSet rs;

	while (paramNames.hasMoreElements()) {
	    String paramName = (String)paramNames.nextElement();
	    String[] paramValues = request.getParameterValues(paramName);
	    if (paramValues.length==1) {
		String paramValue = paramValues[0];
		if (paramValue.length()==0) {
		    paramP=false;
		} else {
		    if (paramName.equals("imgid")) {
			identify = paramValue; // 画像の登録ID not fileoid
		    }
		}
	    }

	}

	// (パラメータ || fileoid )がない場合にエラーを画像で表示
	if (!paramP) {
	    identify="21"; //埋め込みのエラーコード
	}
	if (this.opendb()) {
	    sql="SELECT fileoid FROM images WHERE identify='"+identify+"'";

	    try {
		stmt = conn.createStatement();
		rs = stmt.executeQuery(sql);
		//this.log("sql: "+sql+"\n");
		int testid=0;
		while (rs.next()){
		    testid = rs.getInt("fileoid");
		    String number = (new Integer(testid)).toString();
		    //this.log("fileoid:"+number+"\n");
		    if (testid == 0) {
			identify="21";
		    }
		}
		if (testid == 0) {
		    identify="21";
		}
		
		conn.close();
	    } catch (SQLException sqlex) {
		sqlex.printStackTrace();
		identify="21";//エラー画像を表示するように強制
		//this.log(identify);
	    } finally {
		if (conn!=null) {
		    try {
			conn.close();
		    } catch  (SQLException sqlex) {
			sqlex.printStackTrace();
		    }
		}
	    }
	}

	sql="SELECT fileoid,mimetype FROM images WHERE identify='"+identify+"'";

	if (this.opendb()) {
	    try {
		lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();
		conn.setAutoCommit(false);
		stmt = conn.createStatement();
		rs = stmt.executeQuery(sql);

		while (rs.next()) {
		    int obid = rs.getInt("fileoid");
		    String mime = rs.getString("mimetype");
		    
		    // jdbc-binary-data.html
		    LargeObject lo = lobj.open(obid, LargeObjectManager.READ);
		    byte buf[] = new byte[lo.size()];
		    lo.read(buf,0,lo.size());

		    //Imageに直して送信
		    response.setContentType(mime);
		    DataOutputStream dos = new DataOutputStream(response.getOutputStream());
		    dos.write(buf,0,lo.size());

		    lo.close();
		    dos.close();
		}
		
		rs.close();
		conn.setAutoCommit(true);
		conn.close();

	    } catch (SQLException sqle) {
		sqle.printStackTrace();
		
	    } finally {
		if (conn != null) {
		    try { conn.close(); } catch (Exception e) { e.printStackTrace(); }
		}
	    }
	} else {
	    // Cant open db
	    PrintWriter out = response.getWriter();
	    response.setContentType("text/html");
	    out.println("Cant open databse");
	    out.close();
	}
	
    }
    
}

このサーブレットを呼び出すように,JSPでくり返しデータベースから,情報を引き出して,リンクを生成するということで,簡単に画像一覧を出力できた.

エラー画像のidを間違えて,絵がでなかったという,あり勝ちな失敗もメモ.

エラー画像を動的に生成する方が美しいのだが,それはまた別の機会に.

一覧をidで見せる美しくないJSP

<jsp:useBean id="myImageList" scope="session" class="sfjt.ImageList" />

<%
Vector list = myImageList.getList("image/jpeg");
for (int c=0; c<list.size() ; c++) {
   String number = (String) list.get(c);
   out.println("<li><a href=\"/sfjt/servlet/ImgFromDB?imgid=");
   out.print(number);
   out.print("\">"+number+"</a>");
}
%>

画像ファイルにコメントを追加して,アルバムにしてみよう

当り前の一覧だけではあまりにも使い勝手が悪いので,画像の順次表示機能と,それぞれの画面に対するコメントを追加できるようにする.

CREATE TABLE image_memo (
        identify INT; -- imageと一致させる
        dflag BOOLEAN default false;
	date timestamp default CURRENT_TIMESTAMP;

	keyword TEXT; -- 2k max
	comment TEXT; -- 2k max

	category TEXT; --2k max
        -- フラグが必要かもしれないので調べて作る
)

多くのファイルの中をsedで書き換える

20040521

223個あるファイルの一行を書き換える必要があったら,どうしよう?

シェルスクリプトを書いて処理させるのがスマートだね.

perlという回答もあるけど,小さなツールを組み合わせて,目的の処理をスマートにこなすのが,Unix使いというものだ.

1. シェルスクリプトで使う部品を作る

2. シェルスクリプトの骨格を作る

処理手順を考える

特定のファイル名の一覧を変数に格納する

繰り返し (ファイル名一覧を1つづつ)
       「1つのファイルを処理する」
繰り返しの終わり

上述の手続きをシェルスクリプトで記述するためには,次のように記述する.

先頭の#!/bin/shは,ファイルを作ってシェルスクリプトを実行させるときに,必要である.
#!/bin/sh

FILES=`ls *.jsp`

for FILE in $FILES
do
       cp $FILE $FILE.bak
       sed "s/<jsp:forward page=\"login.html\" \/>/<% response\.sendRedirect(\"http:\/\/www\.hoge\.hoge\/login\.html\"); %>/" $FILE > $FILE.new
       mv $FILE.new $FILE
done

必ずバックアップを作成するために,cp $FILE $FILE.bakという部分を作成しておかないと,後で泣くこともある.

viなどで,上述のファイルを適切なファイル名で作成し,chmod 755として実行権を与える.

実例

$ vi ~/usr/bin/change1Line.sh
$ chmod 755 ~/usr/bin/change1Line.sh

2つほど,実際に作業するファイルを~/tmpなどの実験用ディレクトリに移動し,作成したプログラムを動作させてみる.

失敗には取り返しの付かないものがあるので,ファイルを対象にした処理をするときは,慎重にも,慎重を重ねなければならない.

実例

$ cp sample.jsp sample2.jsp ~/tmp
$ cd ~/tmp
$ ls *.jsp
sample.jsp sample2.jsp
$ ~/usr/bin/change1Line.sh
$ ls *.jsp *.jsp.bak
sample.jsp sample.jsp.bak sample2.jsp sample2.jsp.bak
$ diff sample.jsp sample.jsp.bak
7c7
>     <% response.sendRedirect("http://www.hoge.hoge/login.html"); %>
---
>     <jsp:forward page="login.html" />

動作していることを確認したら,作業用のファイルを速やかに消す.

実例

$ rm ~/tmp/*jsp*

いよいよ,本物のファイル群を対象にした処理を開始する.

それでも失敗があると困るので,ローカルにバックアップを作る.

$ cd target
$ ls backup.taz
ls: backup.taz: そのようなファイルやディレクトリはありません
$ tar cvfz backup.taz ./*.jsp
$ ls backup.taz
backup.taz

それではいよいよコマンドの実行

$ ~/usr/bin/change1Line.sh

動作を確認しよう

失敗していたら,作ってしまったファイルを削除し,backup.tazから復活させると良い.


本筋とは関係のないメモ

perl

perl, スクリプト言語の雄であるが,これでプログラムを書くと,後にコードを読むときに苦痛を感じることが多いので,好まない.

いわゆるHackするのには,良いスクリプト言語なのだが,もう少しなんとかならないのか?と思う点もある.これはツールの設計の信念にかかわる問題で,perlはシステム運用のレポートを自動的に作りたかった作者がhackして作り上げた言語なので,「使う奴は,なにをするか判っている唯一人のプログラマ」という前提があるような気がする.

某研究室ではこれが公式言語であり,卒論発表のたびに喧嘩を売るような質問をしてしまい,反省している.


Unix生活 プログラム 環境 ツール カーネル

Mail address of me