Windows用Perl(ActivePerl)のインストール

Windows PC向けのPerl処理系としては、 ActiveStateで配布されているActivePerlが最も有名だと思います。 ここでコンパイル済み処理系の.zipまたは.msiが配布されているので、 インストーラを起動してインストールできます。 インストール先のデフォルトは "C:\Perl" ですが、直下に置くのが嫌なら変更できます。 PATHにperl.exeのパスは自動的に追加されます。 また、.plファイルをエクスプローラでダブルクリックするとperl.exeに解釈されるように関連付けが行われますが、 私はテキストエディタが起動するように書き換えました。


Perlの覚え書きメモ

ファイルの文字列置換(-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はどこだ…」となりがちですね。

$a =~ s/^0+//; $a += 0;

ファイルグロブ演算子のよくある注意点
ファイルグロブ演算子(<>)を使う場合、 <*.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);

Misc. Topics Top

(first uploaded 2001/01/25 last updated 2011/05/08, URANO398)

Gポイントポイ活 Amazon Yahoo 楽天

無料ホームページ 楽天モバイル[UNLIMITが今なら1円] 海外格安航空券 海外旅行保険が無料!