「初めてのPerl」 11章 (ファイルハンドルとファイルテスト)

家を出るのが面倒でせっかく当選したライヴに行かなかったbonlifeです。最低です…。休日はほぼ引きこもりです…。ということで、せっかく家にいるので、ちまちまと進めているPerlのお勉強の続きをやってみました。ようやく「初めてのPerl」の11章ですよ。われながら遅いっ!11章の内容はだいたい以下のような感じです。

  • ファイルハンドルの説明
  • dieによる致命的エラー発生
  • warnによる警告メッセージ表示
  • デフォルトのファイルハンドルの変更
  • ファイルテスト
  • stat関数とlstat関数
  • ビットストリング
  • 特別な下線ファイルハンドル

なるほど、なるほど、といった感じで読み進めることができました。と思いましたが、練習問題を解いてみると、案外理解できていないことに気づきました…。ということで、早速練習問題のbonlife的(not 模範)解答例です。
ex11-1.pl

  • ユーザから入力ファイル名、出力ファイル名、サーチパターン、置き換え文字列を対話的に入力させる
  • 入力ファイルを読み込んで、見つかったサー日パターンを置き換え文字列に置き換えて、出力ファイルへと書き出す
#! perl
use strict;
use warnings;

# 変数の宣言
my $input_file, my $output_file, my $search_pattern, my $replace_string;

# 入力ファイル名の入力
print "Please input an input file name : ";
while (<>) {
    chomp;
    $input_file = $_;
    if ( -f $input_file  && -r $input_file ) {
        last;
    } else {
        print "$input_file is not an readable file.\n";
        print "Please input an input file name : ";
    }
}

# 出力ファイル名の入力
print "Please input an output file name : ";
OUTPUT_INPUT : while (<>) {
    chomp;
    $output_file = $_;
    # $input_fileと$output_fileが同名の場合、再入力
    if ( $input_file eq $output_file ) {
        print "Output file must be different from input file.\n";
        print "Please input an output file name : ";
    # $output_fileが空文字列の場合、再入力
    } elsif ( $output_file =~ /^\s*$/ ) {
        print "Please input an output file name : ";
    # $output_fileが存在し、書き込み可能な場合、上書き確認
    } elsif ( -e $output_file && -w $output_file ) {
        print "\"$output_file\" already exists.\n";
        print "Do you really want to overwrite it? (Y/anything) : ";
        while (<>) {
            chomp;
            # Y または y の場合は上書きOK
            if ( /^\s*y\s*$/i ) {
                last OUTPUT_INPUT;
            # それ以外の文字列の場合、処理を終了
            } else {
                print "Stopped processing.\n";
                exit;
            }
        }
    # $output_fileが存在しない場合
    } elsif ( ! -e $output_file ) {
        last;
    # その他の場合
    } else {
        print "\"$output_file\" is not a writable file.\n";
        print "Please input an output file name : ";
    }
}

# サーチパターンの入力

print "Please input a search pattern : ";
while (<>) {
    chomp;
    $search_pattern = $_;
    if ( $search_pattern =~ /^\s*$/) {
        print "Please input a search pattern : ";
    }
    else {
        last;
    }
}

# 置き換え文字列の入力

print "Please input a replace string : ";
while (<>) {
    chomp;
    $replace_string = $_;
    last;
}

print "Start replacing...\n";
print "INPUT FILE     : ", $input_file,     "\n";
print "OUTPUT FILE    : ", $output_file,    "\n";
print "SEARCH PATTERN : ", $search_pattern, "\n";
print "REPLACE STRING : ", $replace_string, "\n";

open INPUT , "< $input_file"
    or die "Can't open $input_file. ($!)";
open OUTPUT, "> $output_file"
    or die "Can't open $output_file. ($!)";

while (<INPUT>) {
    $_ =~ s/$search_pattern/$replace_string/g ;
    print OUTPUT $_;
}
close INPUT ;
close OUTPUT;

出力結果は以下の通り。

  • wilma.txt
Wilma always sleeps.
Am I Wilman?
No, I'm Wilma.
WILMA is so big!
Oh, small wilma...
ex11-1.pl
Please input an input file name : wilma.txt
Please input an output file name : wilma_output.txt
Please input a search pattern : (wilma|WILMA)
Please input a replace string : Wilmo
Start replacing...
INPUT FILE     : wilma.txt
OUTPUT FILE    : wilma_output.txt
SEARCH PATTERN : (wilma|WILMA)
REPLACE STRING : Wilmo
  • wilma_output.txt
Wilma always sleeps.
Am I Wilman?
No, I'm Wilma.
Wilmo is so big!
Oh, small Wilmo...

解答例を見て気づいたことは以下の通り。

  • ユーザ入力を処理する部分はサブルーチンにした方が良い
  • $source, $destなんて変数を使った方がちょっとそれっぽい
  • 置換後の文字列では、特別な文字は展開されない

まだまだこざっぱりしたソースは書けませんが、動くものが出来たのでオーケーオーイェーということで。続いて2問目。
ex11-2.pl

  • コマンドラインからファイル名のリストを受け取る
  • その1つ1つについて、読み出し可能か、書き込み可能か、実行可能か、存在しないかを表示するプログラムを書く
#! perl
use strict;
use warnings;

# 引数の確認

if ( $#ARGV == -1 ) {
    print "Please specify more than 1 argument(s).\n";
    exit;
}

# 引数のリストが空になるまで処理を実行

while ( $#ARGV != -1 ) {
    &simple_file_test(shift(@ARGV));
}

# ファイルテストのサブルーチン

sub simple_file_test {
    # 存在するかどうか
    if ( -e $_[0] ) {
       print "$_[0] exists.\n";
    } else {
       print "$_[0] doesn't exist.\n";
    }
    # 読み出し可能かどうか
    if ( -r $_[0] ) {
       print "$_[0] is readable.\n";
    } else {
       print "$_[0] is not readable.\n";
    }
    # 書き込み可能かどうか
    if ( -w $_[0] ) {
       print "$_[0] is writable.\n";
    } else {
       print "$_[0] is not writable.\n";
    }
    # 実行可能かどうか
    if ( -x $_[0] ) {
       print "$_[0] is executable.\n";
    } else {
       print "$_[0] is not executable.\n";
    }
}

出力結果は以下の通り。

ex11-2.pl
Please specify more than 1 argument(s).
ex11-2.pl test01.txt test02.txt test99.txt
test01.txt exists.
test01.txt is readable.
test01.txt is writable.
test01.txt is not executable.
test02.txt exists.
test02.txt is readable.
test02.txt is writable.
test02.txt is not executable.
test99.txt doesn't exist.
test99.txt is not readable.
test99.txt is not writable.
test99.txt is not executable.

解答例を見て気づいたことは以下の通り。

  • ファイルが存在しない場合、その場でreturnした方が良い
  • テスト結果を配列にpushして、まとめて表示した方が良さそう
  • 特殊なファイルハンドルであるアンダーラインを使った方が動作が速い

サブルーチンの使い方を忘れかけていたので復習しながら解いてみました。ふむふむ。returnは上手く使いこなせていませんでしたね。続いて、3問目。
ex11-3.pl

  • コマンドラインからファイル名のリストを受け取って、もっとも古いファイルの名前とその古さを日数単位で表示するプログラムを書く
#! perl
use strict;
use warnings;

# 引数の確認

if ( $#ARGV == -1 ) {
    print "Please specify more than 1 argument(s).\n";
    exit;
}

# 引数のリストが空になるまで処理を実行

my %list_with_time;

while ( $#ARGV != -1 ) {
    my $file_name = shift @ARGV;
    my $timestamp = -M $file_name;
    if ( -e $file_name ) {
        $list_with_time{"$file_name"} = $timestamp;
    } else {
        print "WAR : \"$file_name\" doesn't exists.\n";
    }
}

# ファイルの情報を1つも取得できなかった場合の処理

if ( ( keys %list_with_time) == 0 ) {
    print "WAR : No file with timestamp specified.";
    exit;
}

# ハッシュのキーを値でソートした配列を作成

my @sorted_file_list = sort {
    $list_with_time{$b} <=> $list_with_time{$a}
} keys %list_with_time;

# 表示処理

printf "The oldest file is \"$sorted_file_list[0]\". : created or modified %d days ago.", $list_with_time{$sorted_file_list[0]};

出力結果は以下の通り。

ex11-3.pl test01.txt test02.txt test99.txt
WAR : "test99.txt" doesn't exists.
The oldest file is "test01.txt". : created or modified 97 days ago.

解答例を見て気づいたことは以下の通り。

  • 引数の有無のチェックはdieを使ってシンプルに書ける
  • 「最高水位線」アルゴリズム(high-water mark algorithm)を使った方がスッキリする

まぁ、こんな感じで一歩ずつ進んでいきます。今日はもうちょっと頑張ってみます!