postgresqlの場合
$ psql --list または $ psql -l
sed "s/^ *[0-9]*/number/" file.txt
sed "s/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/IP Address/" file.txt
sed "s/\([0-9\.]*\) ms/\1/" file.txt
sed "s/\*/-1/" file.txt
sed "s/\!/flag/" file.txt
traceroute.shさすがに一行に収まらない,おまけに組合せが多いので,awkで書き換えることを検討.
利用するテーブル
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>=
製作途上
必要なもの: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で記録を取ることにした.統計的な処理をするために,データベースに格納しておくこととする.
テーブル設計
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で起動するコマンド
作成中
/etc/syslog.confをかきかえて,データを送るようにする.
/usr/local/src/postgresql-7.4.3/contrib/tips/README.apachelogを参考にした.
$ sudo -u postgres createuser nobody
$ sudo -u postgres createdb --encoding=EUC_JP www_log
$ sudo -u postgres psql www_log < apachelog.sql
この定義によって,テーブルが作成され,テーブルにnobodyが書き込める権限を与えられる.
drop table access;
CREATE TABLE access (host char(200), ident char(200),
authuser char(200), accdate timestamp, request char(500),
ttime int2, status int2, bytes int4); -- archive=none;はエラーになった.
-- grant all on access to nobody; --これがマニュアルにあるものだが,
grant select,insert on access to nobody; --として,選択と挿入のみ可にする
LogFormat "insert into access values ( '%h', '%l', '%u',
'%{%d/%b/%Y:%H:%M:%S}t', '%r', %T, %s, %b );" access
TransferLog '| /bin/su -c "/bin/sed \"s/, - );$/, -1 );/\" |
/usr/local/pgsql/bin/psql www_log" nobody'
エラーログにこんなのが残っていたら,エラーを直そう.
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は引数を一つしか取れない.
tomcat5/conf/Catalina/localhost/追加.xml
workers2.propertiesに追加
web.xmlにservletを追加
HelloWorldなど,確実に動くサーブレットで検査
ローカルのスタイルシートで管理する方針に変えた.
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>はシェルより,まだコマンド入力が完成していないことを示す,プロンプトである.
たまたまある一致があるので,ちと焦る.しかも,もっと簡単な方法があった.
# まずローカルに保存 ./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
環境変数の受け渡しに注意.
だったら,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で書き換えよう.
<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
ラージオブジェクトの定義は簡単
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を検索などの操作には利用できないのである.
ファイルそのものの保管のためには,上記程度の情報で十分であろう.
小さいコマンドを組み合わせて,処理をするのが,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で使えるpostgresql関数については, perl5拡張を参照されたい.
#!/usr/bin/perl
use Pg;
## データベースへの接続を行う
my $conn = Pg::connectdb("dbname=sfjt");
if (PGRES_CONNECTION_OK ne $conn->status) {
printf("コネクションエラー");
exit;
}
CGI.pmを使ったファイルアップロードのサンプル,Cepheidさんのページ参考に機能を切り出す,つもりが,CGIとして呼び出されていないと,機能が使えないようなので,却下.
ファイルの種類を推測しますを見る.これが使えそうである.
#!/usr/bin/perl use File::MMagic; $mm = new File::MMagic; $res=$mm->checktype_filename(@ARGV[0]); print $res . "\n";
無事に目的の文字列を取得できたので,コードに埋め込む.
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を後から消せなくなるので,メモしておいた.
#!/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はなかった,戻り値が異なっている可能性もある.継続調査.
引数なしで起動すると,再帰的にカレントディレクトリ以下を探して,存在する全てのファイルを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を間違えて,絵がでなかったという,あり勝ちな失敗もメモ.
エラー画像を動的に生成する方が美しいのだが,それはまた別の機会に.
<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
-- フラグが必要かもしれないので調べて作る
)
223個あるファイルの一行を書き換える必要があったら,どうしよう?
シェルスクリプトを書いて処理させるのがスマートだね.
perlという回答もあるけど,小さなツールを組み合わせて,目的の処理をスマートにこなすのが,Unix使いというものだ.
1. シェルスクリプトで使う部品を作る
$ grep "<jsp:forward page=\"login.html\" \/>" *.jsp 出力を確認して,「1つのファイルを処理するsedによる処理」を作る. $ sed "s/<jsp:forward page=\"login.html\" \/>/<% response\.sendRedirect(\"http:\/\/www\.hoge\.hoge\/login\.html\"); %>/" sample.jsp | more
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, スクリプト言語の雄であるが,これでプログラムを書くと,後にコードを読むときに苦痛を感じることが多いので,好まない.
いわゆるHackするのには,良いスクリプト言語なのだが,もう少しなんとかならないのか?と思う点もある.これはツールの設計の信念にかかわる問題で,perlはシステム運用のレポートを自動的に作りたかった作者がhackして作り上げた言語なので,「使う奴は,なにをするか判っている唯一人のプログラマ」という前提があるような気がする.
某研究室ではこれが公式言語であり,卒論発表のたびに喧嘩を売るような質問をしてしまい,反省している.