[Perl入門に戻る]
(perlの第1歩) (出力の工夫) (ファイルの読み込みと行の処理) (ファイルの入出力--補足) (文字列の処理)

ファイルの読み込みと行の処理

一つの事項が1行に記述されているものを考えます。 1行1行読んでいって、それをどこかに記憶しておくのもひとつの方法ですが、 日々のデータを出力するとき、 いちいちデータを最初からよんで該当する日の行を読み込んで処理するほうが 実行時間はかかりますが、スクリプトを書くのは簡単かもしれません。 その月に31日あれば、ひと月分のカレンダーを作るのに、データファイルを 31回読むわけです。

まず簡単のためデータファイル名をdata.txtに固定します。 次のような形式のものを扱うことにします:例(EUC)

## data.txt for calendar
## "month/day message" or "month/day/year message" 
 4/20     創立記念日Hol!

 2/5      太郎誕生日
11/22     次郎誕生日

 3/11/2000 7:00pm 渋谷ハチ公前で待ち合わせ
 4/13/2000 5:00pm 池袋いけふくろう前で待ち合わせ
 5/23/2000 数学レポート締切

まず「月/日」または「月/日/年」形式の日付をかき、 その後ろに1個以上のスペースをはさんで該当日のメッセージをかきます。

このファイルを開き、1行1行読んでいく操作は次のようになります:

open (DATA, "data.txt") || die "cannot open data.txt .. $!\n"; 
while ($line = <DATA>) {
    .......... (作業をする)
}
close DATA;

最初の行で、めざすファイルを DATA というスクリプト中で用いる名前(ファイル・ハンドルという)で開きます。 そのファイルが存在しないなどの理由で開けない場合はエラーメッセージを出して終了します。

次に while のループがあります。( ) の中は DATA から1行読み込みその内容を末尾の改行文字も含んだまま $line という変数に代入します。 その値は、読み込めない(ファイルが終わった)場合を除き「真」ですので、 このループは、ファイルの中身がある限り続きます。 読み込んだデータ($line)を用いてブロックの中で作業が行なわれます。

while ループが終了したら(ファイルの終わりにきたら)、close コマンドで閉じます。

実際には、指定した年・月・日に対応する情報を値として返す関数 getmsg を作ることにします:

sub getmsg {
    local($year, $month, $day) = @_;
    local($msg);
    local($line);
    if ($day eq "  ") { return ""; }
    open (DATA, "data.txt") || return "";
    while ($line = <DATA>) {
        if ($line =~ /^\s*(\d{1,2})\/(\d{1,2})\/?(\d{4})?\s+(.*)/) {
            unless ( .......................................... ) {
                $msg = $msg . $4 . "<BR>";
            }
        }
    }
    close DATA;
    return $msg;
}

サブルーチンでは、まず、3つの引数を $year, $month, $day の3つの局所変数に格納します。 また、読み込む行を格納する $line と該当するデータのメッセージを順に追加して格納する $msg の2つの局所変数も用意します。

その月の正規な日でない $day に対しては 空文字列を値として返して、 サブルーチンを終了します。 つぎにデータファイルを DATA というファイル・ハンドルで開き、 もしファイルが開けない場合はデータがないということで、空の文字列を値として返してサブルーチンを終了します。

もしファイルが開けた場合は、順に1行ずつ $line に読み込みます。 その行が指定の形式に適合する場合のみチェックの作業を行ないます。

       $line =~ /^\s*(\d{1,2})\/(\d{1,2})\/?(\d{4})?\s+(.*)/
の =~ がマッチング演算子であることはすでに学びました。 左辺が右辺の条件に適合する場合「真」の値をとります。 右辺の // で挟まれた部分にはマッチングのパターンが正規表現で表示されます。 正規表現に関しては次の表を御覧下さい。 ただし、以下で「英数字」というときは
       A B ... Y Z a b ... y z _ 0 1 2 ... 8 9
を指し、「空白文字」は(半角の)空白・タブ・改行・復帰・改頁を指します。

  + ? . * ( ) [ ] { } |
  \ ^ $ / 以外の文字
その文字自身
  先頭の^ 文字列の先頭 -- 例 ^apple
  末尾の$ 文字列の末尾 -- 例 apple$
  . 改行を除く任意の1文字にマッチする
  (...)括弧内のパターン要素をひとつの要素(グループ)にまとめる
n番目のグループにマッチした部分文字列は$nで表現
  + 直前のパターン要素に1回以上マッチする
  ? 直前のパターン要素に0回または1回マッチする
  * 直前のパターン要素に0回以上マッチする
  {N,M} 直前のパターン要素にN回以上M回以下マッチする
  {N} 直前のパターン要素にちょうどN回マッチする
  {N,} 直前のパターン要素にN回以上マッチする
  [...] 文字クラスのどれかひとつにマッチする --例 [A-Z]
  [^...] クラスに属さない文字ひとつにマッチする --例 [^A-Z]
  (...|...|...) 選択肢のどれかひとつにマッチする
  \英数字以外の文字特別な意味を失い普通の文字になる -- 例 \\, \(, \*, ...
  \w 英数字にマッチする
  \W 非英数字にマッチする
  \b 単語の境界にマッチする
  \B 単語の境界以外にマッチする
  \s 空白文字にマッチする
  \S 空白文字以外にマッチする
  \d 数字にマッチする
  \D 数字以外にマッチする
  \n 改行(new line)
  \r 復帰(return)
  \f 改頁(form feed)
  \t タブ
  \1 \2 ... ( )でまとめたグループにマッチしたサブパターン

もし複数の部分文字列にマッチする場合は最も左で、しかも最も長い文字列がマッチします。

上の getmsg 中の正規表現

        ^\s*(\d{1,2})\/(\d{1,2})\/?(\d{4})?\s+(.*)
の場合、 がこの順に並んでいるとマッチします。 この場合最後の文字列は改行の手前まで全部にマッチしようとします。 なお、スラッシュ(/)は正規表現の区切りに用いられているので、 それと区別するためエスケープして \/ と記述しています。

マッチした場合、かっこでくくられている部分が上のように順に $1, $2, $3, $4 という特殊変数に格納されます。 ($1, $2, ... 以外にも正規表現自体がマッチした部分文字列は $&、 それより前の部分は $`、それより後の部分は $' という変数に格納されます。 これらも便利です。)

$1, $2, $3 のデータが考えている日に適合するときは、 $4 . "<BR>" を $msg に追加します。 そのチェックは上で ............. と略されていますので、自分で考えてみましょう。

あとは表の出力の途中で上の関数を呼び出してやれば、 データを取りこんだ表ができるでしょう。

もし難しければ、データの構造をもっと限定すると、スクリプトの作成が楽になります。 各自工夫して下さい。 また、上のように随分無駄にファイルの開閉を行うのはいやだと思う場合は、 各日付ごとの情報を配列に記憶させる方法で、スクリプトを作ってみて下さい。


[Perl入門に戻る]