【Perl】ターミナル画面でテキスト検索結果を KWIC で表示する
2014-06-24-2
[Programming]
先日書いた「文字列から半角N文字分取る方法」[2014-06-19-2]を使って、grep のようなテキスト走査による検索結果をターミナル画面に KWIC 形式で表示する Perl プログラムを書きました。
KWIC とは KeyWord In Context の略で、この場合は、中心に検索キー、左右にコンテキスト(前後の文字列)を配置するというものです。これらをターミナル画面にずれないように表示するためには、検索キーとコンテキスト文字列の正確な長さ(半角文字数)が必要になります。そのため前述の半角N文字分取る方法が必要になるのです。
何はともあれ実行例から。
■実行例:
ツイッターのログからキーワード「大喜び」で検索し、出てきた順に表示。
右コンテキストでソート("-s r")。左でソートしたい場合は "-s l"、逆順ソートは "-r"。
大量の英文ファイルを集めてきて、このスクリプトで検索&表示すれば、今は亡き「英語例文検索 EReK」のような英作文支援システムになります。
■コード (kwicgrep.pl):
KWIC とは KeyWord In Context の略で、この場合は、中心に検索キー、左右にコンテキスト(前後の文字列)を配置するというものです。これらをターミナル画面にずれないように表示するためには、検索キーとコンテキスト文字列の正確な長さ(半角文字数)が必要になります。そのため前述の半角N文字分取る方法が必要になるのです。
何はともあれ実行例から。
■実行例:
ツイッターのログからキーワード「大喜び」で検索し、出てきた順に表示。
% ./kwicgrep.pl '大喜び' tweets.csv _や器からタレや肉まで始終絶賛、[大喜び]でした。両親は国立新美術館をみ_ _がとうございました。とらちゃん[大喜び]でした。またご機嫌とらちゃんと_ _はサンタさんからのプレゼントに[大喜び]。「サンタさんもらった」「だい_ 白優勝! うちの白オタマトーンも[大喜び](うそ)"______________________ _ーダーに登録してもらえると私が[大喜び]です。"________________________
右コンテキストでソート("-s r")。左でソートしたい場合は "-s l"、逆順ソートは "-r"。
% ./kwicgrep.pl -s l '大喜び' tweets.csv _はサンタさんからのプレゼントに[大喜び]。「サンタさんもらった」「だい_ _がとうございました。とらちゃん[大喜び]でした。またご機嫌とらちゃんと_ _や器からタレや肉まで始終絶賛、[大喜び]でした。両親は国立新美術館をみ_ _ーダーに登録してもらえると私が[大喜び]です。"________________________ 白優勝! うちの白オタマトーンも[大喜び](うそ)"______________________
大量の英文ファイルを集めてきて、このスクリプトで検索&表示すれば、今は亡き「英語例文検索 EReK」のような英作文支援システムになります。
% ./kwicgrep.pl -s l 'enabled' /usr/share/doc/bash/bash.html | head ll option in the list will be [enabled] before_______________________ __________________________are [enabled], non-zero otherwise. When se ble Completion</B> above) are [enabled]._____________________________ <B>Pathname Expansion</B> are [enabled]._____________________________ _____________<I>names</I> are [enabled]. For example, to use the____ ro if all <I>optnames</I> are [enabled]; non-zero____________________ ___________________________If [enabled], history expansion will be pe _____________shell option, if [enabled], causes the shell to attempt _____________________Names of [enabled] shell builtins.______________ ____A colon-separated list of [enabled] shell options. Each word in_
■コード (kwicgrep.pl):
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Encode;
use open ':utf8';
binmode STDOUT, ":utf8";
binmode STDIN, ":utf8";
use Getopt::Long;
my $term_width = 70;
my $sort_lr_context = ""; # for sort
my $reverse_mode = 0; # for sort
GetOptions (
'width=s' => \$term_width,
"sort=s" => \$sort_lr_context,
"reverse" => \$reverse_mode,
);
my $key = shift @ARGV;
$key = Encode::decode_utf8($key) if not utf8::is_utf8($key);
my ($dummy, $klen) = get_first_n_hkchars($key, 100);
my $context_width = int(($term_width - ($klen + 2)) / 2);
my @matches;
while (<>) {
chomp;
next if not /^(.*?)($key)(.*)$/;
my ($lc, $rc) = ($1, $3);
my ($lstr, $llen) = get_last_n_hkchars($lc, $context_width);
my ($rstr, $rlen) = get_first_n_hkchars($rc, $context_width);
my $lfill = "_" x ($context_width - $llen);
my $rfill = "_" x ($context_width - $rlen);
my $ostr = "$lfill$lstr"."[".$key."]"."$rstr$rfill";
if ($sort_lr_context) {
if ($sort_lr_context =~ /^l/) {
push @matches, [join("", reverse split(//, $lstr)), $ostr];
} elsif ($sort_lr_context =~ /^r/) {
push @matches, [$rstr, $ostr];
}
} else {
print "$ostr\n";
}
}
if ($sort_lr_context) {
if ($reverse_mode) {
@matches = sort {$b->[0] cmp $a->[0]} @matches;
} else {
@matches = sort {$a->[0] cmp $b->[0]} @matches;
}
foreach my $l (@matches) {
print $l->[1]."\n";
}
}
# 先頭から半角N文字分取る
# 取った文字列と実際の長さ(半角文字数)を返す
sub get_first_n_hkchars {
my ($str, $n) = @_;
my $s = "";
my $slen = 0;
foreach my $c (split(//, $str)) {
my $clen = 2;
if (
$c =~ /\p{InBasicLatin}/
or
($c =~ /\p{InHalfwidthAndFullwidthForms}/ and $c =~ /\p{Katakana}/)
) {
$clen = 1;
}
last if $slen + $clen > $n;
$slen += $clen;
$s .= $c;
}
return ($s, $slen);
}
sub get_last_n_hkchars {
my ($s, $n) = @_;
my $sr = join("", reverse split(//, $s));
my ($str, $slen) = get_first_n_hkchars($sr, $n);
my $rv = join("", reverse split(//, $str));
return ($rv, $slen);
}
この記事に言及しているこのブログ内の記事
