ケーズメモ このページをアンテナに追加 RSSフィード

2007-05-15

『初めてのPerl』11章 ファイルハンドルとファイルテスト

ポイントと雑感

ファイルハンドルとは?

本書によればファイルハンドルとは「PerlプロセスI/Oコネクションに対してPerlプログラムが付けた名前のこと」とのこと。注意点としてはファイルハンドルはファイル名ではないということ。ファイルハンドルは大文字で指定することが薦められている。

他の構文にも特殊な例があったようにファイルハンドルにも特殊なファイルハンドルがある。本に書いてあったのは以下の6つ。

ファイルハンドル名意味
STDIN標準入力のファイルハンドル
STDOUT標準出力のファイルハンドル
STDERR標準エラー出力のファイルハンドル
DATA__END__以降に書かれているデータのファイルハンドル
ARGVARGVを順番に呼び込むためのファイルハンドル

後、ARGVOUTが書いてあるけれどこれの使い方がイマイチよく分からない。雰囲気としては@ARGVに出力するファイルハンドルで、検索して見つかった内部処理らしきプログラムにもそう使ってあったけど他に用途があるのかな。@ARGVなら直接代入でも変更できるし……。

ファイルハンドルの使い方

ファイルハンドルからファイルを開くにはopenをファイルハンドルを閉じるにはcloseを使う。openには4つのオプションがある。

open FILE, "tmp";   # 読み込み(ディフォルト値)
open FILE, "<tmp";  # 読み込み
open FILE, ">tmp";  # 書き込み(上書き)
open FILE, ">>tmp"; # 追記モード

と、本には書いてあるけれど以前にopenはファイルハンドル、モード、ファイル名の3引数で使ったほうがいいと書いてあったような記憶があったのでperldocで調べてみた。

$ perldoc -f open

として調べてみると

open FILEHANDLE,EXPR
open FILEHANDLE,MODE,EXPR
open FILEHANDLE,MODE,EXPR,LIST
open FILEHANDLE,MODE,REFERENCE

とやはり3引数。ついでにファイルハンドル自体も変数で扱ってよいみたい。うーん、この本自体2003年出版なのでその間に変わっていることもあるのかもしれないなぁ……。10章で挙げた

Perlベストプラクティス

Perlベストプラクティス

が2006年出版なのでそちらの方でファイル操作の話を確認してみよう。

[追記]

「ケーズメモ - 『続・初めてのPerl』8章 ファイルハンドルへのリファレンス

http://d.hatena.ne.jp/ksmemo/20070524/p2

で問題は解決しました。openは三引数でファイルハンドルとして変数を使う(間接ファイルハンドル)こともきちんとできるとのこと。

[/追記]

ファイルオープンに失敗した場合のエラー処理としてはPerlではdieまたはwarnと、システムへの要求が失敗したときに値がセットされる特殊変数$!を使う。例えばこんな感じ

open FILE, ">", "tmp.txt" or die "Cannot open: $!";

これでオープンに失敗したときの理由が標準出力に表示される。試しにパーミッションが000のファイルをオープンしようとしてみた。するとこんなエラーメッセージが出た。

Cannnot open: Permission denied at file_open_test line XX.

読み出し権限がないということでファイルのオープンに失敗したということがきちんと表示されました。

dieとwarnの違いはdieはエラーが起こった時点でプログラムが実行停止されるのに対し、warnは警告を出すだけでプログラムの実行自体は続けられるというもの。

ファイルハンドルの使い方

ファイルからデータを読み込むときにはオープンしたファイルハンドルの両端をSTDINと同じように<>でくくって使う。また、printなどにファイルハンドルを指定して、そこに書き込ませることもできる。

例えばaccess.logというファイルの中からrootを含む行を抜き出して、root_access.logというファイルを新たに作りたいとすると

#!/usr/bin/perl -w

use strict;

my IN;
my OUT;

open IN, ">", "access.log" or die "Cannnot open: $!";
open OUT, "<", "root_access.log" or die "Cannnot create file: $!";

while (my $line = <IN>) {    # access.logの内容を一行づつ読み込む
    chomp($line);
    if ($line =~ /\broot\b/) {
        print OUT "$line\n"; # root_access.logに書き込み
    }
}

close OUT;
close IN;

こんな感じか。ファイル名はハードコーディングするよりはコマンドラインから引数が取れたほうがいいので雑なプログラムだけど。ファイルハンドルの名前もINとOUTなんてのは我ながらあまり付けないほうがいい気もします。

printのこの書き方で分かったのは普段はprint STDOUTを省略してprintと書いているということ。このディフォルトで使われているファイルハンドルを変更するにはselectを使う。これを使えばいちいちファイルハンドルを指定しなくてもそれ以降のprintはselectで指定したファイルハンドルに対して書き出しを行ってくれる。

ファイルテスト

ファイルに対する情報を習得するための方法で大量にあるため省略。練習問題で必要になったものだけ書くことにする。

stat関数とlstat関数、localtime関数

stat関数とlstat関数はファイルテストだけでは手に入らない詳細な情報をそれぞれの関数システムコールすることで入手する。ということは必然的にWindowsには無いんじゃないだろうか……私の使ってるWindowsCygwinやらMinGWやら入っているのでWindowsにはないとはっきり言い切れないなぁ……。たぶん無いと思うけど。

localtime関数タイムスタンプから情報を取り出すための関数。それでもいろいろ違うところがある。多くはインデックスが0スタートなのでひとつ前にずれているということ。

ビット演算子
演算子意味
&AND演算子
OR演算子
^XOR演算子
<<左シフト
>>右シフト
~否定演算子

そう言えばPerlだったかは忘れたけどFlip-Flopを再現するプログラムを見た気がする。しかし、回路屋さんはPerlじゃなくてC/C++を使ってると思うのでどういった局面で使うのかな?学生が論理回路の確認するときには使えそうではあるけど。

下線ファイルハンドル

_で$_や@_のファイルハンドル版といったところか。前回に読み込んだファイルハンドルの内容をそのまま使う場合に使える。当然、前回読み込んだものなので、きちんと制御していないと予想と違ったものが出てくる可能性があるのは言うまでも無い。

練習問題

ex11-1
#!/usr/bin/perl -w

use strict;

# 入力ファイル名を入力
print "Enter input file name: ";
chomp(my $input_file_name = <STDIN>);

# 出力ファイル名を入力
# ファイルがすでに存在したら終了
print "Enter output file name: ";
chomp(my $output_file_name = <STDIN>);
die "Cannot overwrite existing file." if -e $output_file_name;

# 置換パターンを入力
print "Enter search pattern: ";
chomp(my $search_pattern = <STDIN>);

# 置換後のパターンを入力
print "Enter replase pattern: ";
chomp(my $replace_pattern = <STDIN>);

# 入出力ファイルオープン
open my $rfh, "<", "$input_file_name"
    or die "Cannnot open '$input_file_name': $!";
open my $wfh, ">", "$output_file_name"
    or die "Cannot open '$output_file_name': $!";

# 置換作業
while(<$rfh>) {
    $_ =~ s/$search_pattern/$replace_pattern/g;
    print $wfh $_;
}

# 入出力ファイルクローズ
close $wfh;
close $rfh;

途中で自分が書いたサンプルで「ファイルハンドルにINとOUTはないんじゃないかなー」と書いてたら解答は普通に使ってた。いいのか?

私は毎回chompをしているけれど解答はそこを関数化していた。確かにそのほうがよさそうだけど、それならIO::Prompt使いたいなぁ。

追加点としては、設問には無かったけれど入力ファイルが存在しなかった場合にも一応エラーチェックを入れておいた。存在しないファイル名でファイルをオープンすると空のファイルが作られるので、それに対してマッチを行うというのもどうかと思ったので。

問題文に置換パターンの中でバックスラッシュ文字や特殊記号が使えるかということを書いてあったので試してみたら変数展開されていなかった。解答によれば展開は1段階しか行われないため、パターンの中に変数が使われているとその変数を展開するだけで、変数の中にあるものは展開されないとのこと。展開したければperlfaqを読めと書いてあったので、perldoc perlfaqを調べるとperlfaq6が正規表現FAQだったのでperldoc perlfaq6として調べてみたけどよく分からなかった。どこ調べれば良かったんでしょうね。

ex11-2
#!/usr/bin/perl -w

use strict;

sub file_check {
    my $file_name = shift;

    print "Checking '$file_name'...\n";

    if (-e $filename) {
        print "'$file_name' exists.\n";
        print "'$file_name' is readable.\n" if -r $file_name;
        print "'$file_name' is writeable.\n" if -w $file_name;
        print "'$file_name' is executable.\n" if -x $file_name;
        print "\n";
    }
    else {
        print "'$file_name' does not exist.\n";
    }
}

foreach $file (@ARGV) {
    file_check($file);
}

解答は属性を配列に入れていって最後にjoinするという形にしていた。確かにそちらのほうがスマートな感じ。

ex11-3
#!/usr/bin/perl -w

use strict;

die "This promgram need more than one argument.\n" unless @ARGV;

my $oldest_file_name = shift @ARGV;
my $last_update = -M $oldest_file_name;

foreach (@ARGV) {
    if($last_update < -M $_) {
        $oldest_file_name = $_;
        $last_update = -M $_;
    }
}

print "'$oldest_file_name' is the oldest file.\n";
printf "Last update is %d\n", $last_update;

解答はforeachの中身をリスト代入でやってた。これはうまい。後、日数だけでいいかと思って小数点以上だけしか使わなかったけど、小数点1位まで取っていた。確かに0日では困るのでもう少し情報がほしいところ。しかし、0.1日=2.4時間=2時間24分以上経過していないと判別できないという状態。変換は小数点以下は単純に引いていく方法以外ぱっと思いつかないので略。丸め誤差とかどうなるんだろうか。


まとめ

  • ファイルハンドルの宣言と使い方について学んだ
    • 宣言方法とファイルハンドルの扱いについては要・追加調査
  • ファイルテストの使い方について学んだ

ファイルが扱えるようになったので7章で書いた簡易版grepをもう少し改良できそう。

#!/usr/bin/perl -w

use strict;

die "Usage: my_grep PATTERN [FILE]..." if scalar @ARGV < 2;

my $pattern = shift @ARGV;
my $rfh;

while (@ARGV) {
    my $file_name = shift @ARGV;

    open $rfh, "<", $file_name;

    while (<$rfh>) {
        chomp;

        if (/$pattern/) {
            print "$file_name line $. : $_\n";
        }
    }

    close $rfh;
}

これでマッチしたファイル名と行数は表示できるようになった。ex11-1で明らかになったようにこのままだと展開が必要な文字はまだ使えないし、オプションはまったく使えないわけなんですが。とりあえずさっと形になったというところでしょうか。


スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証