|
Windows用Perl(ActivePerl)のインストール |
Windows PC向けのPerl処理系としては、
ActiveStateで配布されているActivePerlが最も有名だと思います。
ここでコンパイル済み処理系の.zipまたは.msiが配布されているので、
インストーラを起動してインストールできます。
インストール先のデフォルトは "C:\Perl" ですが、直下に置くのが嫌なら変更できます。
PATHにperl.exeのパスは自動的に追加されます。
また、.plファイルをエクスプローラでダブルクリックするとperl.exeに解釈されるように関連付けが行われますが、
私はテキストエディタが起動するように書き換えました。
●ファイルの文字列置換(-iオプション)
例えば、たくさんのテキストファイルに含まれる
「AM 6:00」を全て「PM 9:00」に、「Morning」を「Night」
に変換して、同じファイル名で書き出したい、というときはPerlの-iオプションがすごく便利です。
この場合は、次のようなスクリプトを作ります。(a.plとしましょう)
while(<>){
s/AM 6:00/PM 9:00/g;
s/Morning/Night/g;
print;
}
|
そして、
shell% perl -i.orig a.pl *.txt
|
とすると、それぞれのファイルの処理前の内容が「ファイル名.orig」
という名前で退避され、処理後の内容が元の「ファイル名」
として置き換わります。
上のように、Perlの引数でグロブ(ワイルドカード)
を指定すれば、たくさんのテキストファイルを一気に処理できます。
●何度書いても覚えられないlocaltime
むー、この年、月、日…の並び順が覚えられないんですよね。
# 現在の時刻を YYYYMMDDHHMMSSで返します。
sub nowstr(){
($s, $mi, $h, $d, $mo, $y, $w) = localtime(time);
$mo++; $y += 1900;
return sprintf("%4d%02d%02d%02d%02d%02d", $y, $mo, $d, $h, $mi, $s);
}
# ファイルの編集日時をYYYYMMDDHHMMSSで返します。
sub filetimestr(){
local($filename) = @_;
if(! -f $filename){ return undef; }
$filetime = time - (-M $filename) * 24 * 60 * 60;
($s, $mi, $h, $d, $mo, $y, $w) = localtime($filetime);
$mo++; $y += 1900;
return sprintf("%4d%02d%02d%02d%02d%02d", $y, $mo, $d, $h, $mi, $s);
}
|
西暦年を得るには1900を足すことに、月は1を足す必要があることに小注意。
●timelocal関数、または2つの日付の差(日数)を求める
localtime関数と逆に、年月日、時分秒の組からtime秒数を返すには、
Time::Localモジュールのtimelocal、timegmの両関数を使います。
下の例は、コマンドラインから2つの日付をYYYYMMDDで受け取って、その間の日数を出力するツールです。
use Time::Local;
($date1, $date2) = @ARGV;
if($date1 =~ /(\d\d\d\d)(\d\d)(\d\d)/){
$time1 = timelocal(0,0,0,$3,($2-1),($1-1900));
} else { die "$date1 was not recognized, expected a date formatted by YYYYMMDD."; }
if($date2 =~ /(\d\d\d\d)(\d\d)(\d\d)/){
$time2 = timelocal(0,0,0,$3,($2-1),($1-1900));
} else { die "$date2 was not recognized, expected a date formatted by YYYYMMDD."; }
$d = abs($time1-$time2) / (24 * 60 * 60);
print "$d\n";
|
timelocal関数の引数の並び順は秒、分、時、日、月、年です。
「年」は西暦から1900を引くことに、「月」は我々現実社会の月から1を引き0〜11で指定することに大注意!
●左0詰の数値表現から数値を10進数で読み取る
PerlにLTRIM、RTRIM系の関数ってありましたっけ?ていうかあるべきだ!(なんじゃそりゃ)
ないものは仕方ないので愚直に正規表現を使う例が以下です。
こんなのそのままやんといえば身もふたもないですが、
例えばPHPなどのプログラミングに慣れている時にPerl世界に戻ってくると
「trim、trim、trimはどこだ…」となりがちですね。
●ファイルグロブ演算子のよくある注意点
ファイルグロブ演算子(<>)を使う場合、
<*.html>のように、ファイルパターンを直に書く場合はよいのですが、
ファイルパターンを変数で指定する場合、必ず下のように
変数名を{ }で囲みます。
$g = "*.html";
#ダメっす @files = <$g>;
@files = <${g}>;
foreach (@files){
print "$_\n";
convertFile($_);
}
|
●ディレクトリのファイル一覧を得る
opendirとreaddirを使うと、
ディレクトリに含まれるファイルエントリの一覧が取得できます。
但しその中には「.」と「..」も入っているので、
通常それらは処理対象から外すことに注意が必要です。
opendir(INDIR, ".") || die $!;
@files = readdir(INDIR);
closedir(INDIR);
foreach (@files){
if(/(.+)\.(html)$/){
$new = "$1.htm";
print "$new\n";
rename($_, $new) || die $!;
}
}
|
上のスクリプトは、カレントディレクトリに含まれる「*.html」
というファイルを「*.htm」に変更する例です。
●ファイル名からディレクトリ名だけを切り出す
File::Basenameというモジュールを使います。
use File::Basename;
$dir = dirname($filepath);
|
ディレクトリ名を取り出すにはdirname関数、
主ファイル名を取り出すにはmainname関数を使います。
●置換演算子の2つのオプション
置換対象の文字列が改行文字を途中に含む場合は、「s」オプションをつけます。
置換対象を最長一致ではなく最短一致でマッチさせたい場合は、
正規表現の繰り返し指示子(*、+、?)の後ろに?をつければOKです。
s/RRR(.*?)RRR/<font color=\"red\">\1<\/font>/sg;
|
これは、$_のRRRとRRRで囲まれた部分をHTMLのfont colorタグに置き換えています。
●ファイルのコピー
use File::Copy;
if(-f $target){ unlink($target) || die $!; }
copy($source, $target) || die $!;
|
Windowsの場合には、「C:/」で始まるなど、ドライブ名を書かないと
「No such file or directory」というエラーになってしまうようです。
●grepの代わりをするワンライナー
grepコマンドでは、
OSにもよりますがこの正規表現をエスケープさせるのにどう書けばいいのか、
若干混乱します。
Perlならとりあえずシングルクォートで囲めば、
中は普通のPerlスクリプトと同じように書けば、
どんなOSでも同じように動く(はず)です。
%shell perl -ne 'print if(/error\.txt/ && ! /admin/)' process.log
|
上の例ではprocess.logファイルから、"error.txt"
という文字を含み、"admin"という文字は含まない行を出力しています。
-eはワンライナーを、
-nはコマンドラインで渡した入力を1行ずつループしながら処理することを表すオプションです。
代わりに-pを使う(-pe)と、
ワンライナーの演算結果($_)を標準出力に出力する処理も行ってくれます。
●先頭行を削除するワンライナー
%shell perl -nle 'print if($. > 1)' indata.txt
|
変数$.には、現在処理している行番号が入ります。(先頭が1)
-lは、入力を自動的にchompし、print後にまた自動的に改行してくれるオプションです。
●ファイルの一括改名のテンプレート
カレントディレクトリの*.jpgが空白文字を含む場合、半角ハイフンに置き換えるサンプルです。
@files = <*.jpg>;
foreach (@files){
$infile = $outfile = $_;
$outfile =~ s/ /\-/g;
print "[$infile] to [$outfile]\n";
rename($infile, $outfile) || die $!;
}
|
●require文が検索するディレクトリ
require文が検索するディレクトリのリストは、@INCに格納されています。
プログラム中からこれを操作することもできます。
push(@INC, 'C:/usr');
require 'indate-common.pl';
|
●再帰的にディレクトリを検索するサンプル
下のサンプルは指定したディレクトリ以下を再帰的に検索し、ファイル名を表示します。
$level = 0;
sub showdir {
my($indir) = @_;
opendir(INDIR, $indir) || die $!;
my(@files) = readdir(INDIR);
closedir(INDIR);
foreach (@files){
if($_ ne '.' && $_ ne '..'){
$path = $indir . "/" . $_;
for($i=0; $i<$level; $i++){ print " "; }
print $_ . ((-d $path) ? "/" : "") . "\n";
if(-d $path){
$level++; & showdir($path);
}
}
}
$level--;
}
& showdir("C:/usr/lang");
|
もう少し実用的な例を。下のサンプルは指定したディレクトリ以下を再帰的に検索し、
ある文字列(例では"ZZ9")を含むファイルの名前を表示します。
コメント化していますが、対象とする拡張子を絞るなどの応用もすれば、
いろいろ使えそうです。
use strict;
sub walksub {
my($indir) = @_;
opendir(INDIR, $indir) || die $!;
my(@files) = readdir(INDIR);
closedir(INDIR);
foreach my $fileName (@files){
next if($fileName eq '.' || $fileName eq '..');
my $path = $indir . "/" . $fileName;
if(-d $path){
& walksub($path);
} else {
# next if($fileName !~ /\.txt$/i);
open(INFILE, $path) || die $!;
my @lines = grep /ZZ9/, <INFILE>;
if(@lines){
my $rpath = $path;
$rpath =~ s/^.*forms\///;
print "${rpath}\n";
}
close(INFILE);
}
}
}
& walksub("C:/temptemp/startdir");
|
●タブ区切りテキストファイルをカンマ区切りへ変換する
カンマ区切りにするついでに二重引用符で囲みます。
以下のようにmap関数を活用するととても短く書けます。
while(<>){
chomp; @arr = split;
@arr = map { '"' . $_ . '"' } @arr;
print join(",", @arr) . "\n";
}
|
●いくつかの中級な正規表現記法
例えば次のような文字列から、「[Q]なにがし[A]なにがし」という部分文字列を切り出してみます。
$_ = "[Q]111 [A]222 [Q]333 [A]444 [Q]555 [A]666";
while(/\[Q\](.+?)\[A\](.+?)(?=\[Q\]|\Z)/sg){ print "<$1><$2>\n"; }
|
「.+?」や「.*?」は「.+」や「.*」のバリエーションで、
正規表現の途中(後ろに別のパターンが続く)で使われ、最短一致での照合を行います。
つまり、後続のパターンを「含まない」ことを、「?」一文字で指示できるのです。
「(?=パターン)」は、正規表現の末尾に使われ、そのパターンが後続に現れる文字列に適合しますが、
「パターン」自身は適合部分、つまり「$&」とはみなされず、
「$'」の先頭に回って次回の照合にも使われます。
適合部分に含まれないことから「先読み」と呼ばれる処理です。
親類として、そのパターンが後続に「現れない」ことを条件とする
「(?!パターン)」もあります。先読みしつつ現れることを否定するので「否定先読み」といえます。
では、後続に特定のパターンが現れるか、または文字列の末尾である
(現れずに文字列終端となる)という条件を表現するには?
上の例のように
「(?=パターン|\Z)」が正解。\Zは$と似たようなものですが、
改行文字を含んでいる場合に$は「行末」ですが\Zは「文字列終端」である点が異なります。
//gオプションは、while文の条件式で使い、繰り返し照合することを指示します。
より具体的にいえば、whileループの末尾で「$_ = $'」を行いつつループします。
●ファイル全体を文字列に読み込む
普通のプログラミング言語のように1行ずつファイルハンドルで読んでもいいのですが、
Perlにはもっと短く書ける慣用記法があります。
いくつかあるそうですが、中でも特にジャンキーなのは下のように、
改行文字を表す$/特殊変数をdoブロックの中でundefして読ませる方法だと思います。
open(INFILE, 'sample.txt') || die $!;
$text = do { local $/; <INFILE> };
close(INFILE);
|
(first uploaded 2001/01/25 last updated 2011/05/08, URANO398)
|