主な機能は、指定したキーワードを見易いようにANSIエスケープシーケンスでマーキングすることです。ログを読むときに、蛍光ペンを引いたようにキーワード部分を読み易くしたくて作成しました。
また、-fオプションを指定することで、行指向フィルターとしても機能します。
プログラムのソースを検索するときは、grepのようにキーワードにマッチした行だけでは文脈を理解できません。そのため、マッチした前後の行も表示させるように実装しています。
デフォルトではマッチした行の前後を5行ずつ表示します。このデフォルトの行数を変更するには、-fの次に数字を指定します。
- 例)「-f」 数値を指定しない場合はデフォルト(前後5行を表示)
- 例)「-f 3」 前後3行を表示
- 例)「-f 0,5」 前は0行,後ろは5行を表示
他にもオプションスイッチがあるのですが、grepのオプションに似せた仕様にしています。ただし、キーワードに使える正規表現はgrepとは異なり、Perlの拡張正規表現です。
実行例
$ mark -inf 0,3 tty /usr/local/bin/mark
*** skip ***
48: if( $main::bIsATty ){
49: if ($main::ignorecase) {
50: if ($mybuff =~ s/($main::re)/\033[1m$1\033[0m/igo) {
51: $match_flg = 1 ;
*** skip ***
97: if( $main::bIsATty ){
98: print(" \033[34m",
99: "*** skip ***",
100: "\033[0m\n") ;
*** skip ***
164: $main::bIsATty = -t STDOUT;
165: ##############
166:}
167:
*** skip ***
183: &prt_variable ('bIsATty', $main::bIsATty) ;
184: &prt_variable ('numbering', $main::numbering) ;
185: &prt_variable ('prt_fname', $main::prt_fname) ;
186: &prt_variable ('opt_nofname', $main::opt_nofname) ;
$
コンソールに表示する際は色付けした表示が見易いのですが、ファイルにリダイレクトする際にはANSIエスケープシーケンスの制御コードが邪魔になる事が多いです。その為、ANSIエスケープシーケンスを抑止するコードを入れています。下記のコードは、抑止するか否かを判定する為のフラグを設定している行です。
$main::bIsATty = -t STDOUT;
ANSIエスケープシーケンスの抑止については『ANSIエスケープシーケンスを使えない環境に対応する』という記事も参照してみて下さい。
実行例(色付けされていない=ANSIエスケープシーケンスの制御コードの出力が抑止されている)
$ mark -inf 0,3 tty /usr/local/bin/mark > /tmp/mark_summary.txt
$ cat /tmp/mark_summary.txt
*** skip ***
48: if( $main::bIsATty ){
49: if ($main::ignorecase) {…(省略)…
167:
*** skip ***
183: &prt_variable ('bIsATty', $main::bIsATty) ;
184: &prt_variable ('numbering', $main::numbering) ;
185: &prt_variable ('prt_fname', $main::prt_fname) ;
186: &prt_variable ('opt_nofname', $main::opt_nofname) ;
$
[mark] Perlスクリプト
- マーカーペンをイメージ、かつ人の名前っぽいので「mark」という名前にしています。
#!/usr/bin/perl -w
################################################################################
## MARK -- emphasizes part matching a pattern
##
## - Author: tomyama 2006-2022
## - Only for personal use !
##
################################################################################use strict ;
use File::Basename ;exit (&pl_main (@ARGV)) ;
##########
## スクリプトのエントリポイント
sub pl_main {## 初期化処理
&init_script();## 引数解析
&parse_arg (@_) ;## デバッグ用 : パラメータ出力
&prt_param () if ($main::debug) ;for (my ($i, $m) = (0, scalar (@main::fi_in)) ; $i < $m ; $i++) {
my $opn_fname = "$main::fi_in[$i]" ;print STDERR (qq{ ***** $opn_fname *****\n}) if ($main::debug) ;
open (FI_IN, "<$opn_fname") ||
die (qq{$main::appname: `$opn_fname': } .
qq{could not open file: $!\n}) ;
my $nr = 0 ;
my $num = 8 ;
if ($main::prt_fname) {
$num = (((int (length ($opn_fname) / 8)) + 1) * 8) - 1 ;
}
while (<FI_IN>) {
$nr++ ;
my $mybuff = $_ ;
$mybuff =~ s/\r?\n$//o ;## マーキングする
my $match_flg = 0 ;
if( $main::bIsATty ){
if ($main::ignorecase) {
if ($mybuff =~ s/($main::re)/\033[1m$1\033[0m/igo) {
$match_flg = 1 ;
}
} else {
if ($mybuff =~ s/($main::re)/\033[1m$1\033[0m/go) {
$match_flg = 1 ;
}
}
}else{
if( $main::ignorecase ){
if( $mybuff =~ m/($main::re)/igo ){
$match_flg = 1;
}
}else{
if( $mybuff =~ m/($main::re)/go ){
$match_flg = 1;
}
}
}## マッチしていなかったら
if ($main::opt_filter && ! $match_flg) {
## 後方行の出力
$main::opt_filter_e-- ;
if ($main::opt_filter_e >= 0) {
goto PRINTOUT ;
}## 前方行をバッファに溜めておく
## opt_filter_pre=0 の場合はバッファは必要無し
if ($main::opt_filter_pre == 0) {
next ;
## バッファが満杯であれば整理しておく
} elsif (scalar (@main::opt_filter_buff) >=
$main::opt_filter_pre) {
shift (@main::opt_filter_buff) ;
}## バッファに溜める
push (@main::opt_filter_buff, $mybuff) ;
next ;
## マッチしていたら
} elsif ($main::opt_filter) {
## 読み易いように、skip...を出力
if ($main::opt_filter_e +
scalar (@main::opt_filter_buff) <
0) {
if( $main::bIsATty ){
print(" \033[34m",
"*** skip ***",
"\033[0m\n") ;
}else{
print(" *** skip ***\n");
}
}## バッファを吐き出す
for (my ($i, $nri, $m) = (
0,
$nr - scalar (@main::opt_filter_buff),
$#main::opt_filter_buff);
$i <= $m; $i++, $nri++) {
## 必要に応じてファイル名を出力
if ($main::prt_fname) {
printf ("%-${num}s:",$opn_fname) ;
}
## 必要に応じて行番号を出力
if ($main::numbering) {
printf ("%7d:", $nri) ;
}
## 出力する
print ("$main::opt_filter_buff[$i]\n") ;
}
undef (@main::opt_filter_buff) ;## 後方行出力用のカウンタをセットする
$main::opt_filter_e = $main::opt_filter_post ;
}PRINTOUT:
## 必要に応じてファイル名を出力
printf ("%-${num}s:",$opn_fname) if ($main::prt_fname) ;## 必要に応じて行番号を出力
printf ("%7d:", $nr) if ($main::numbering) ;## 出力する
print ("$mybuff\n") ;
}
close (FI_IN) ;
}return 0 ;
}##########
## 初期化処理
sub init_script{
### GLOBAL ###
$main::apppath = dirname ($0) ;
$main::appname = basename ($0) ;
$main::debug = 0 ;
$main::numbering = 0 ;
$main::prt_fname = 0 ;
$main::opt_nofname = 0 ;
$main::opt_filter = 0 ;
$main::opt_filter_pre = 0 ;
$main::opt_filter_post = 0 ;
#@main::opt_filter_buff ;
$main::opt_filter_e = 0 ;
$main::ignorecase = 0 ;
$main::re = undef ;
#@main::fi_in ;
## [ANSIエスケープシーケンス]を使うか否かの判定で使う
$main::bIsATty = -t STDOUT;
##############
}##########
## デバッグ用 : パラメータ出力
sub prt_param {
my $c = 20 ;
local *prt_variable = sub ($$) {
printf STDERR (qq{PARAM : %-${c}s = "%s"\n}, uc ($_[0]),
$_[1] ? 'True' : 'False') ;
} ;print STDERR (qq{ ***** PARAMETER *****\n}) ;
printf STDERR (qq{PARAM : %-${c}s = "%s"\n},
uc ('apppath'), $main::apppath) ;
printf STDERR (qq{PARAM : %-${c}s = "%s"\n},
uc ('appname'), $main::appname) ;
&prt_variable ('debug', $main::debug) ;
&prt_variable ('bIsATty', $main::bIsATty) ;
&prt_variable ('numbering', $main::numbering) ;
&prt_variable ('prt_fname', $main::prt_fname) ;
&prt_variable ('opt_nofname', $main::opt_nofname) ;
&prt_variable ('opt_filter', $main::opt_filter) ;
printf STDERR (qq{PARAM : %-${c}s = "%s"\n},
uc ('opt_filter_pre'), $main::opt_filter_pre) ;
printf STDERR (qq{PARAM : %-${c}s = "%s"\n},
uc ('opt_filter_post'), $main::opt_filter_post) ;
&prt_variable ('ignorecase', $main::ignorecase) ;
printf STDERR (qq{PARAM : %-${c}s = '%s'\n},
uc ('Regular_Expressions'), $main::re) ;
for (my ($i, $m) = (0, scalar (@main::fi_in)) ; $i < $m ; $i++) {
printf STDERR (qq{PARAM : %-${c}s = "%s"\n},
uc ("fi_in($i)"), $main::fi_in[$i]) ;
}
}##########
## 引数解析
sub parse_arg {
my @val = @_ ;## 引数分のループを回す
while (my $myparam = shift (@val)) {
if ($myparam =~ s/^-([a-zA-Z])([a-zA-Z]+)$/-$1/o) {
unshift (@val, "-$2") ;
}## デバッグモードOn
if ($myparam eq '-d' || $myparam eq '--debug') {
$main::debug = 1 ;
} elsif ($myparam eq '-f') {
$main::opt_filter = 1 ;
$main::opt_filter_pre = 5 ;
$main::opt_filter_post = 5 ;
if (defined ($val[0]) && $val[0] =~
m/^([0-9]+)(?:,([0-9]+))?$/o) {
## 捨てる
shift (@val) ;$main::opt_filter_pre = $1 ;
if (defined ($2)) {
$main::opt_filter_post = $2 ;
} else {
$main::opt_filter_post = $1 ;
}
}
} elsif ($myparam eq '-H' || $myparam eq '--with-filename') {
$main::prt_fname = 1 ;
} elsif ($myparam eq '-h' || $myparam eq '--no-filename') {
$main::opt_nofname = 1 ;
} elsif ($myparam eq '--help') {
&usage (0) ;
exit (0) ;
} elsif ($myparam eq '-i' || $myparam eq '--ignore-case') {
$main::ignorecase = 1 ;
} elsif ($myparam eq '-n' || $myparam eq '--line-number') {
$main::numbering = 1 ;
} else {
if (! defined ($main::re)) {
$main::re = $myparam ;
} else {
if ("$myparam" eq '-') {
## dummy
} elsif (! -f "$myparam") {
warn (qq{$main::appname: `$myparam': },
"file not found.\n") ;
&usage (1) ;
exit (1) ;
} elsif (! -r "$myparam" && ! -l "$myparam") {
warn (qq{$main::appname: `$myparam': },
"permission denied.\n") ;
&usage (1) ;
exit (1) ;
}
push (@main::fi_in, $myparam) ;
}
}
print STDERR (qq{ARGV : "$myparam"\n}) if ($main::debug) ;
}if (! defined ($main::re)) {
warn ("$main::appname: ",
"Please specify the Regular Expressions.\n") ;
&usage (1) ;
exit (1) ;
}if ($#main::fi_in < 0) {
#&usage (1) ;
#exit (1) ;
push (@main::fi_in, '-') ;
} elsif ($#main::fi_in > 0) {
$main::prt_fname = 1 if (! $main::opt_nofname) ;
}
}##########
## 書式表示
sub usage ($) {
my $msg = "usage: " .
"$main::appname [<OPTIONS...>] <PATTERN> [<FILE...>]\n" .
"Try `perldoc $main::apppath/$main::appname' for more information.\n" ;if ($_[0]) {
print STDERR ($msg) ;
} else {
print STDOUT ($msg) ;
}return 0 ;
}
__END__=pod
=head1 NAME
MARK - emphasizes part matching a pattern
=head1 SYNOPSIS
$ mark [I<OPTIONS...>] I<PATTERN> [I<FILE...>]
=head1 DESCRIPTION
The "B<mark>" behaves like the marker pen.
The specified I<PATTERN> is searched out and that part is emphasized.The I<PATTERN> can be described by the Regular-Expression equal with B<Perl>.
I<FILE>s specifies the input file name.
If it is a standard input, "B<->" is given.=head1 OPTIONS
=over 4
=item -d, --debug
Debugging mode is on.
=item -f [I<num-forward>[,I<num-rear>]]
It behaves like the filter program.
The back and forth 5 lines are displayed in default.=item --help
Simple help is displayed.
=item -h, --no-filename
Suppress the prefixing of filenames on output when multiple files are searched.
=item -H, --with-filename
Print the filename for each match.
=item -i, --ignore-case
Ignore case distinctions in the I<PATTERN>.
=item -n, --line-number
Prefix each line of output with the line number within its input file.
=back
=head1 ADVANCED USAGE
$ rpm -qa | mark '-[0-9]+[a-z]?\..+$'
$ mark '\b\d{1,3}(?:\.\d{1,3}){3}\b' /var/log/maillog
$ mark -nf 5,0 '(ServerName|DocumentRoot|Log)\s+.*$' /etc/httpd/conf/httpd.conf
$ mark -iHnf 0,10 '^[^\s].*$' *.{c,h}
$ mark -ni '</?(table|tr|th|td)[^>]*>' index.html | S<less -R>
$ man perlfunc | mark -nf 5,10 -i 'regular expr' | S<less -R>
$ man awk | perl -ne 's/.\010//go; print' | S<mark -nf 0,3 '^[A-Z]'>
$ tail -f /var/log/httpd/access_log | S<mark -f 0 '"(GET|POST) /(?:\S+(?:\.html|\.htm|/))? [^"]+"'>
$ ls -tr /var/log/messages.?.gz | xargs gzip -dc | mark -ihf 10 'error' - /var/log/messages > /tmp/report.txt
=head1 SEE ALSO
When you want to examine the regular expression,
please refer to an online manual of B<Perl>.=over 4
=item L<perlre>(1)
Perl regular expressions
=item L<perlrequick>(1)
Perl regular expressions quick start
=item L<perlreref>(1)
Perl Regular Expressions Reference
=item L<perlretut>(1)
Perl regular expressions tutorial
=item L<perlfaq6>(1)
Regular Expressions
=item regex(7)
POSIX 1003.2 regular expressions
=back
To look for the above-mentioned, I executed the following command.
`man -k regular'
Other more basic references
=head1 BUGS
* none noted
Report bugs to tomyama.
=head1 AUTHOR
Written by tomyama at Oct 2006
=cut
NAME
MARK - emphasizes part matching a pattern
SYNOPSIS
$ mark [OPTIONS...] PATTERN [FILE...]
OPTIONS
-f [num-forward[,num-rear]]
It behaves like the filter program. The back and forth 5 lines are
displayed in default.--help
Simple help is displayed.-h, --no-filename
Suppress the prefixing of filenames on output when multiple files
are searched.-H, --with-filename
Print the filename for each match.-i, --ignore-case
Ignore case distinctions in the PATTERN.-n, --line-number
Prefix each line of output with the line number within its input
file.
ADVANCED USAGE
$ rpm -qa | mark '-[0-9]+[a-z]?\..+$'
$ mark '\b\d{1,3}(?:\.\d{1,3}){3}\b' /var/log/maillog
$ mark -nf 5,0 '(ServerName|DocumentRoot|Log)\s+.*$'
/etc/httpd/conf/httpd.conf$ mark -iHnf 0,10 '^[^\s].*$' *.{c,h}
$ mark -ni '</?(table|tr|th|td)[^>]*>' index.html | less -R
$ man perlfunc | mark -nf 5,10 -i 'regular expr' | less -R
$ man awk | perl -ne 's/.\010//go; print' | mark -nf 0,3 '^[A-Z]'
$ tail -f /var/log/httpd/access_log |
mark -f 0 '"(GET|POST) /(?:\S+(?:\.html|\.htm|/))? [^"]+"'$ ls -tr /var/log/messages.?.gz | xargs gzip -dc | mark -ihf 10 'error'
- /var/log/messages > /tmp/report.txt