| Top Page | プログラミング | Perl 目次 | prev | next | 索引 |

10. ファイル名を指定してファイルの読み書きをする

Perl のプログラムを起動するときのコマンドライン引数として ファイル名を与えて,これを <> という窓口を 通して読み込むという例がいくつも出てきました (4. ファイルからの入力とsplit)). また,欲しい計算結果を画面に表示させるようにプログラムを書いて, その出力をリダイレクトすることでファイルへ書き込んでいました.

このページでは,プログラム中でファイル名を指定してファイルの読み書き をする方法を解説します.これで,多量のデータファイルをどんどん 読み込んで処理したり,その結果を別々のデータファイルに書き込んだり といった作業が効率的にできます.

ファイルハンドルを用意する

ファイルからデータを読んだりデータを書き込んだりする時には, データのやりとりを管理する窓口を経由します. この窓口は,どのファイルのどの部分を今読んでいるのか・書いているのかを 覚えていたり,やりとりするデータを一時的に貯めておくバッファ機能を 持っていたりしますが,その詳細を知る必要はありません.

この窓口に名前をつけたものをファイルハンドルと呼びます. ファイルを「開け」て窓口すなわちファイルハンドルを用意するには, open という関数を使います.

open (FILE, "data001.txt");

これで,data001.txt という名前のファイルを読み込み用に開けて,これを FILE という名前のファイルハンドルに結びつけたことになります.ファイルハンドル の名前は自由に付けられますが,大文字を使うことが慣例になっているようです.

ところで,読み込み用に開こうとしたファイルがカレントディレクトリ (>DOS窓の使い方) に存在しないと,open に失敗することになります.この場合はどうするか. 失敗した場合,open 関数は値として「偽」を返します.これを利用して条件判断をすれば, ファイルを開けなかった場合にはそれなりのメッセージを表示するなどの対処を プログラム中に書くことができます.

open (FILE, "data001.txt") or die "Failed to open a file\n";

この例では, or という演算子を使っています.これは短絡演算子 ともよばれるもので,or の左と右にあたれられた式の真偽の論理和 (どっちかが真ならば真,どっちも偽のときだけ偽)を返すものです (and という演算子もあります).

で,どう「短絡」なのかというと,まず左の式を調べてみて,それが真だったら 全体として真だと決まってしまう(どちらかいっぽうが真なら全体として真だから) ので,右側の式を評価するのを「短絡的に」やめてしまうのです.

式の評価を評価するとき,そこに関数を含んだ式があればその関数も呼び出しますから, 右側の式を’実行する’ことになります. このことを利用すると,or の左の式が偽のときにだけ or のあとの式を実行するという, if 文のような使い方ができます.それが上の例です.これは if 文を使って書いた下の プログラムと同じことを表現しています.

if (! open (FILE, "data001.txt") ) {
    die "Failed to open a file\n";
}

短絡演算子 or を使った書き方は,常套句としてよく使われます. ...... or die という表現は,英語としてそのまま読めば 「……しろ,さもなきゃ死んじまえ」という穏やかでない表現ですが, 意味はよく分かりますね.

ところで,and と or と同じ意味の論理演算子に && と || があります. ただし,これらのほうが and や or よりも結合順位が高いという点が違います

結合順位とは,式のなかに複数の演算子があるときどっちから先に計算していくか の優先度の順位です(厳密には計算の順序ではなくて,式を解釈するときの順位). たとえば,+ と * では * のほうが優先順位が高いので,3 + 2 * 4 は (3 + 2) * 4 ではなくて 3 + (2 * 4) と解釈されます.

open (FILE, "data001.txt") or die; のように open という関数にわたす引数を () で囲っていれば or でも || でも 同じように動作しますが,この括弧を省略して open FILE, "data001.txt" || die; と書くと, "data001.txt" || die が先に評価され,その評価結果が open に2番めの引数として渡されるという, 意図とはまったく異なる動作をしてしまいます. 結合順位に自信がないところは,カッコで囲って意図を明確に表現しよう.


ファイルハンドル経由でデータを読み込む

では, ファイルからの入力と split のページの最初に出した(コマンドラインで指定したファイルからデータを 読み込んで行番号付きで表示する)プログラムを, プログラム中でファイル名を指定するように改変してみます.

$file_name = "data001.txt";
open (FILE, $file_name) or die "Failed to open $file_name\n"; # 「変数展開」のワザ

$no = 1;                      #  行番号カウント用の変数の初期設定.

while ($line = <FILE>)  {     #  ファイルハンドル FILE を介して一行読む.
    print $no, "\t", $line;   #  行番号,タブ,行の内容.
    ++$no;                    #  行番号カウンタを増やす.
}

close (FILE);                 # 「開いた」ファイルを「閉じる」

print "There are ", ($no - 1), " lines.\n";  #  全行数を表示.

コマンドラインで指定したファイルを読み込むとき, $line = <> という 表現を使いましたが,ファイルハンドルをはっきり指定して読み込むときは, <> のなかにファイルハンドルを書きます. ファイルを最後まで読んでしまって,これ以上データがないときには 未定義値が返ってくるのは, $line = <> の場合と同様です.

ファイルの読み書きが終わったら,close 関数を使ってファイルを「閉じて」 おきます.ただし,プログラムの実行を終了するときに Perl の処理系が 開きっぱなしのファイルは閉じてくれるので,上の例の close (FILE) はなくてもかまいません.

上のプログラムでは,変数展開というワザがはじめて出てきています. die "Failed to open $file_name\n"; というところです. 変数展開は,二重引用符(ダブルクオート) "" で囲まれた文字列のなかに変数名があると, その部分は変数の_値_に置き換わるという仕組みです. 一重引用符 '' で囲んだ場合はこの仕組みは働きません.

"Failed to open $file_name\n" は, 文字列連結演算子であるピリオドを使って "Failed to open " . $file_name . "\n" と書いても同じことですが,一組の引用符のなかにみんな書いてしまうほうが 簡単だし読みやすいでしょう.

なお,文字列中のどこまでが変数名かはっきりしない(変数名の直後の文字列が 変数名の続きに見えてしまう)場合には,変数名の部分を {} で囲みます.

$adjective = "smart";

print "He is a $adjective guy.\n";  #  → He is a smart guy.
print "She is $adjectiveer.\n";     #  → She is . (変数 $adjectiveer は未定義)
print "She is ${adjective}er.\n";   #  → She is smarter.


ファイルハンドル経由でデータを書き込む

今度はファイルへの書き込みです.データをファイルに書き込みたいときは, open でファイルを開くときに,これは書き込み用だということを 指定します. ファイル名の前に '>'をつければ既存ファイルがあれば上書きモード, '>>' をつければ既存ファイルがあれば末尾へ追加書き込みする追加モードに なります.

では, コマンドラインで与えたパラメータを使う の最後で紹介した積算温度を計算するプログラムを改変してみます. コマンドラインから与えた温度データファイルの名前のあたまに cum_ という文字列を加えたあたらしいファイルを作り,これに温度と積算温度を並べて 書き出すようにします.

温度データの形式は配列を活用するのところで 紹介したものと同じで,各行にひとつづつ温度データがならんでいます.

# deg_day2.pl

if (@ARGV < 2) {  # 引数の数をチェック(配列をスカラーコンテキストで評価)
    die "USAGE:  perl deg_day2.pl biol_zero  temperature_data_file\n";
}

$zero = shift @ARGV;                 # 生物学的0度を取得
$source_file = shift @ARGV;          # 温度データファイルの名前を取得.
$new_file = "cum_" . $source_file;   # 積算温度記録ファイルの名前を作る.

# もとデータファイルを読み込み用に開く.
open (SOURCE, $source_file) or die "Failed to open $source_file.\n";

# ファイル名のまえに > をつけて,書き込み用にファイルを開く.
open (NEW, ">$new_file") or die "Failed to open $new_file.\n";

$degree_day = 0.0;   #  温度の積算を始めるまえに 0 に初期化(必須ではない).

while ($tmp = <SOURCE>)  {   #  ハンドル SOURCE を介して一行読む.
    chomp $tmp;              # 行末の改行コードを削除.

    if ($tmp > $zero) {      #  閾値より高温だったら…
        $degree_day += ($tmp - $zero); #  高い分だけ積算温度に加算
    }
    print NEW "$tmp\t$degree_day\n";   # 気温と積算温度をファイルに書き込む.
}

close (SOURCE);               # 「開いた」ファイルを「閉じる」
close (NEW);                  #

open (NEW, ">$new_file") でさっそく変数展開のワザが使われていますね.

print 関数のうしろに,書き込み用に開いたファイルのファイルハンドルを書くと, そのハンドルを介して書き込みが行われます.画面には表示されません. ファイルハンドルのうしろにはコンマ ',' がないことに注意してください. この書き込みのところでも "$tmp\t$degree_day\n" のように変数展開を使ってますね.



| Top Page | プログラミング | Perl 目次 | prev | next |