作者: 閑舎
日時: 2002/8/20(11:14)
 皆さん、こんにちは。

 話題にしてました、あるディレクトリ以下に蓄積された .txt, .htm, .html 
から全部をつなげたインデックスファイルを作るスクリプト txtindex、それを
使って、ある文字列を検索表示するスクリプト txtsearch を改良し、まあこん
なところかなというところに達したので、公開します。いずれも Perl が必要で
す。

--------^ txtindex ( date:02-08-20 time:10:35 ) ------------< cut here
#!/usr/bin/perl
#  txtindex  Ver.0.4
#    Copyright 2000/09/28 - 2002 閑舎(Kansha) raku@...
#    このプログラムは無保証です。配布等は自由にして構いません。
#    This program follows GPL.
#  * _ で始まらず小文字の .txt, .htm, .html でおわるファイルが対象。
#  * 100,000 行で 000text を 001text にするなどインデックスファイルを追加していく。

###  必要なパッケージ
require 'jcode.pl';

die "Usage: txtindex directory\n" if($#ARGV != 0);
$dir = shift;
$BASEDIR = $dir;
$BASEDIR =~ s#[/\\]$##;
$INDDIR = "$BASEDIR/00index";

mkdir($INDDIR, 0700) unless(-d $INDDIR);

# txt_ind  ファイル開始の際のtxt.txtの総行数  開始ファイル名
# txt_txt  各テキストファイルをEUC化した内容
$num = 0;
$line_max = 100000;
$txt_ind = "$INDDIR/000index";
$txt_txt = sprintf("$INDDIR/%03dtext", $num);

push @yet, $dir;

while($dir = pop(@yet)){
  &get_sub_dir($dir);
}

open(TXT_IND, ">$txt_ind") or die;
open(TXT_TXT, ">$txt_txt") or die;
$line_num = 0;
for $directory (@sub_dir){
  &mk_ind_dirs($directory);
}
close(TXT_IND);
close(TXT_TXT);
open(NUM, ">$INDDIR/000num");
print NUM "$num\n";
close(NUM);
chmod(0600, $txt_ind, $txt_txt, "$INDDIR/000num");
exit 0;

sub get_sub_dir {
  my($directory) = @_;
  my $path, $file, @dir;
  $directory =~ s/\/$//o;
  opendir(DIR, $directory) || exit(1);
  push @sub_dir, $directory;
  @dir = readdir(DIR);
  foreach $file (@dir){
    next if($file =~ /^[.]{1,2}$/);
    $path = "$directory/$file";
    if(-d $path){
      push(@yet, $path);
    }
  }
  closedir(DIR);
}

sub mk_ind_dirs {
  my($directory) = @_;
  my $path, $file, @dir;
  $directory =~ s/\/$//o;
  opendir(DIR, $directory) || exit(1);
  @dir = readdir(DIR);
  foreach $file (@dir){
    if($file !~ /^_/o and ($file =~ /\.txt$/o or $file =~ /\.html?$/o)){
      $path = "$directory/$file";
      if(-f $path){
        open(IN, $path) || next;
        $path =~ s# #\\ #g;
        printf(TXT_IND "$line_num $path\n");
        while(<IN>){
          chomp;
          $line_num++;
          # Mac形式では一度に全ファイルを読むのでその対策。Unicodeは考えない。
          $_ =~ s/[\r\n]//g;
          &jcode::convert(\$_, 'euc');
          printf(TXT_TXT "$_\n");
          if (($line_num % $line_max) == 0) {
            close(TXT_TXT);
            chmod(0600, $txt_txt);
            $num++;
            $txt_txt = sprintf("$INDDIR/%03dtext", $num);
            open(TXT_TXT, ">$txt_txt") or die;
          }
        }
        close(IN);
      }
    }
  }
  closedir(DIR);
}
--------$ txtindex ( lines:97 ) ----------------------------< cut here

 これは、指定したディレクトリに 00index というサブディレクトリを作り、
そこに必要なファイルを貯めます。Windows だと、

    if($file !~ /^_/o and ($file =~ /\.txt$/o or $file =~ /\.html?$/o)){

は

    if($file !~ /^_/o and ($file =~ /\.txt$/oi or $file =~ /\.html?$/oi)){

のほうが幸せかもしれません。

--------^ txtsearch ( date:02-08-20 time:10:39 ) -----------< cut here
#!/usr/bin/perl
#  txtsearch  Ver.0.4
#    Copyright 2000/09/28 - 2002 閑舎(Kansha) raku@...
#    このプログラムは無保証です。配布等は自由にして構いません。
#    This program follows GPL.
#  * 与える文字列は / のみエスケープした。他は自分で Regular Expression を書く。
#  * string0, string1, ... は AND 検索に限る。

###  必要なパッケージ
require 'jcode.pl';

die "Usage: txtsearch [-b BASEDIR] strings0 [strings1 ...]\n" if($#ARGV < 0);

$BASEDIR = $ENV{'DBASE'};
if($#ARGV >= 0){
  while($ARGV[0] =~ /^-/){
    if($ARGV[0] =~ /^-b$/){
      shift(@ARGV);
      $BASEDIR = $ARGV[0];
      shift(@ARGV);
    }
  }
}
$BASEDIR =~ s#[/\\]$##;
$INDDIR = "$BASEDIR/00index";

$strings[0] = shift;
$strings[0] =~ s#/#\\/#g;
die "Usage: txtsearch [-b BASEDIR] strings\n" if($strings[0] eq "");
&jcode::convert(\$strings[0], 'euc');
$i = 1;
while ($strings[$i] = shift) {
  last if ($strings[$i] eq "");
  $strings[$i] =~ s#/#\\/#g;
  &jcode::convert(\$strings[i], 'euc');
  $i++;
}
$max_str = $i;

# txt_ind  ファイル開始の際のtxt.txtの総行数  開始ファイル名
# txt_txt  各テキストファイルをEUC化した内容
$txt_ind = "$INDDIR/000index";
$line_max = 100000;

open(NUM, "$INDDIR/000num");
$num_max = <NUM>;
chomp($num_max);
close(NUM);

open(TXT_IND, "$txt_ind") or die "There isn't $txt_ind.";
$i = 0;
while(<TXT_IND>){
  chomp;
  if($_ =~ /^(\d+)\s+(.*)$/){
    $num[$i] = $1;
    $path[$i] = $2;
    $i++;
  }
}
close(TXT_IND);
$max_i = $i;

$j = 0;
if ($max_str == 1) {
  for ($i = 0; $i <= $num_max; $i++) {
    $txt_txt = sprintf("$INDDIR/%03dtext", $i);
    open(TXT_TXT, "$txt_txt") or die;
    while(<TXT_TXT>){
      chomp;
      if($_ =~ /$strings[0]/){
        $lines[$j] = $_;
        $nums[$j] = $. + $i * $line_max;
        $j++;
      }
    }
    close(TXT_TXT);
  }
} else {
  for ($i = 0; $i <= $num_max; $i++) {
    $txt_txt = sprintf("$INDDIR/%03dtext", $i);
    open(TXT_TXT, "$txt_txt") or die;
    while(<TXT_TXT>){
      chomp;
      for ($k = 0; $k < $max_str; $k++) {
        if($_ =~ /$strings[$k]/){
          $lines[$j] = $_;
          $nums[$j] = $. + $i * $line_max;
          $kind[$j] = $k;
          $j++;
        }
      }
    }
    close(TXT_TXT);
  }
}
$max_j = $j;

if($max_j > 0){
  for($j = 0; $j < $max_j; $j++){
    $find = 0;
    for($i = 0; $i < $max_i; $i++){
      if($nums[$j] <= $num[$i]){
        $find = 1;
        $file[$j] = $path[$i - 1];
        $lnum[$j] = $nums[$j] - $num[$i - 1];
        last;
      }
    }
    if($find == 0){
      $file[$j] = $path[$max_i - 1];
      $lnum[$j] = $nums[$j] - $num[$max_i - 1];
    }
  }
}
$line = "";
$prt_flg = 0;
$prt_file = "";
$lvl_str = 0;
$real_j = 0;
$tmp_j = 0;
if ($max_str == 1) {
  printf("============================= SEARCH RESULT: %5d =============================\n", $max_j);
  for ($j = 0; $j < $max_j; $j++) {
    printf("%s:%d:\n$lines[$j]\n", $file[$j], $lnum[$j]);
    print "--------------------------------------------------------------------------------\n";
  }
} else {
  $lineok = "";
  for ($j = 0; $j < $max_j; $j++) {
    if ($prt_file ne $file[$j]) {
      if ($prt_flg) {
        $lineok .= $line;
        $real_j += $tmp_j;
      }
      $line = "";
      $prt_flg = 0;
      $prt_file = $file[$j];
      $lvl_str = 0;
      $tmp_j = 0;
    }
    $line .= sprintf("%s:%d:\n$lines[$j]\n", $file[$j], $lnum[$j]);
    $line .= "--------------------------------------------------------------------------------\n";
    $lvl_str++ if ($kind[$j] == $lvl_str);
    $tmp_j++;
    if ($lvl_str == $max_str) {
      $prt_flg = 1;
      next;
    }
  }
  printf("============================= SEARCH RESULT: %5d =============================\n", $real_j);
  $lineok .= $line if ($prt_flg);
  print $lineok;
}
exit 0;
--------$ txtsearch ( lines:154 ) --------------------------< cut here

 これらの使用には jcode.pl が必要です。Web などで検索して、Perl のライ
ブラリディレクトリにいれて下さい。

  /usr/lib/perl5/perlx.x.x/
  c:/perl/lib/

など。また、txtsearch の出力は EUC でなされるので、必要なら、nkf などで
変更してください。nkf がなければ Web から get してください。

例:
  txtsearch -b /home/raku/Ndb "閑舎" Script | nkf -s

 最後に Emacs Lisp のフロントエンドです。GPL です。

(defvar tagjump-output "*Shell Command Output*")
(global-set-key "\C-c\M-s" 'tag-search-strings-in-db)
;;;
;;; txtsearch のフロントエンド
;;;
(defun tag-search-strings-in-db ()
  "Search Strings from DB.

  You need to set the command txtindex and txtsearch before using this macro.
  You can set Strings more than once.
  If you don't want to set Strings any more, push only [Enter].
  And you get the files which have all the Strings.
  You can get all the Strings in search-ring."
  (interactive)
  (let (base str str0 str2)
    (setq base (format "%s" (read-minibuffer "BASEDIR: " "~/Ndb/")))
    (setq base (expand-file-name base))
    (setq str (format "%s" (read-string "Strings: " "")))
    (if (string= str "")
	(message "Strings must be given. Bye!")
      (progn
	(setq str0 str)
	(setq search-ring (butlast search-ring))
	(setq search-ring (cons str0 search-ring))
	(setq str (format "\'%s\'" str))
	(setq str2 " ")
	(while (not (string= str2 ""))
	  (setq str2 (format "%s" (read-string "Strings: " "")))
	  (if (not (string= str2 ""))
	      (progn (setq str (concat str (format " \'%s\'" str2)))
		     (setq search-ring (butlast search-ring))
		     (setq search-ring (cons str2 search-ring)))))
	(setq cmd (concat "txtsearch -b \'" base "\' " str))
	(message "Please wait...")
	(shell-command cmd)
	(switch-to-buffer tagjump-output)
	(delete-other-windows)
	(isearch-forward)
))))

    (setq base (format "%s" (read-minibuffer "BASEDIR: " "~/Ndb/")))

の ~/Ndb/ は環境に応じて変えて下さい。マシンが速ければ、結構こんなデータ
ベースで十分使えます。今のところ、うちは 16MB 強のデータで、単一語検索 
1-2 秒、2 語の AND 検索 10 秒程度です。

--
本田博通(閑舎)
Hiromichi Honda <raku@...>