[2003年度]

Perl 入門 (6)

ファイルの読み込みと文字列の処理

以前ファイルの読み込みについて簡単に学びました。 今回はスクリプトの中でファイル名を指定して読み込み、 それを処理することを学びます。 読み込むファイルは data.txt というファイルにします。 ダウンロードして I:\www に保存して下さい。 内容は架空の学生諸君のテストの点数(100点満点)の一覧表です。 受験しなかった場合は得点欄が「--」となっています。

M03-201  18
M03-202  8
M03-203  75
M03-204  60
M03-205  42
M03-206  29
M03-207  22
M03-208  41
M03-209  9
M03-210  --
M03-211  31
M03-212  34
....
(略)
....
M03-413  67
M03-414  35
M03-415  29
M03-416  65
M03-417  26
M03-418  20
M03-419  62

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

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

最初の行で、めざすファイルを DATA というファイル・ハンドル (スクリプト中で用いる名前)で開きます。 そのファイルが存在しないなどの理由で開けない場合はエラーメッセージを出して終了します。 (「||」は(2)で紹介した「論理和」演算子です。 A || B は、もしAが真ならば B の評価をせずに真の値を返します。 もし、A が偽ならば B を評価し、Bが真ならば真を、Bが偽ならば偽を返します。 したがって、上の場合はもしファイルが開けたら || の後ろは無視されますが、 開けない場合は || の後ろが実行されることになります。)

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

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

それではまず受験しなかった学生の数を求めるスクリプト(nm301.pl)を書いてみます:



 1: open (DATA, "data.txt") || die "cannot open data.txt .. $!\n"; 
 2: $absent = 0;
 3: while ($line = <DATA>) {
 4:     if ($line =~ /--/) { $absent++; }
 5: }
 6: close DATA;
 7: print $absent, "\n";



$absent は受験しなかった人の数を数えるための変数です。
=~ はマッチング演算子といい、 左辺が右辺に記述したパターンにマッチする場合は「真」、 マッチしない場合は「偽」の値を返します。マッチングのためのパターンは いわゆる「正規表現」を用いてふたつのスラッシュ(/)ではさんで記述します。 単に文字列を並べれば、その文字列を含むときに真、含まないときに偽となります。 今の場合は受験していない場合、その該当する行に文字列「--」がありますから、 それを調べているわけです。

正規表現に関しては次の表を御覧下さい。 ただし、以下で「英数字」というときは
       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 ... ( )でまとめたグループにマッチしたサブパターン

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



次のように、データを読み込む変数を指定しないで実行することもできます。 この場合はあらかじめ用意されている特殊変数$_ が用いられます。パターンマッチングの際にも、 対象が$_ の場合は省略することができます:

 1: open (DATA, "data.txt") || die "cannot open data.txt .. $!\n"; 
 2: $absent = 0;
 3: while (<DATA>) {
 4:     if (/--/) { $absent++; }
 5: }
 6: close DATA;
 7: print $absent, "\n";



この場合以外にも$_ はさまざまな場所で使われ、記述を簡単にすることができます。 これに関しては参考書を読んでください。

作業1

上のスクリプトを書き換えて、受験しなかった学生に対応する行だけを画面に出力するスクリプト(nm302.pl) を作りなさい。そのスクリプトと、出力結果を自分のページに載せなさい。

課題をどのように解いたか、「すべて自分でやりました」「M03-XXXの人に少しヒントを貰いました」 「M03-XXX, M03-YYY, M03-ZZZの3人と協力しました」などをはっきり書いて下さい。



今のようにした場合、学生の番号だけでなく、わかりきった「-- 」まで画面に 出力されてしまいます。 番号だけを出力したいので、 各行を「番号」と「得点/--」のふたつにわけることを考えてみましょう。 与えられた文字列を指定した区切りに従って分割するための関数 split を 使うことにします(実はすでに出てきました)。これは次のような形で使います:

  1. @output = split /区切りのパターン/, 対象となる文字列;
  2. @output = split /区切りのパターン/;
  3. @output = split;


区切りのパターンには上で紹介した正規表現を用います。 2のように対象となる文字列を省略した場合は、対象は $_ になります。 さらに 3 のようにパターンも省略した場合は空白文字列がパターンとなります。


それでは次のスクリプト(nm303.pl)を見てみましょう。


 1: open (DATA, "data.txt") || die "cannot open data.txt .. $!\n"; 
 2: while (<DATA>) {
 3:     chomp;
 4:     @elt = split;
 5:     if ($elt[1] =~ /--/) { print $elt[0], "\n"; }
 6: }
 7: close DATA;



特殊変数 $_ を使っています。
3行目の chomp; は行末の改行文字を取り去る働きをします。 ここでは特に必要があるわけではありませんが目に見えないものは消しておいたほうが エラーを防ぐ上で役立つでしょう。 ここでも対象が省略されているので、$_ が対象となっています。
4行目で、文字列の分割を行い、結果を配列@eltに格納しています。 下のスクリプトのように配列を使わないで同じことをすることができます:

(nm304.pl)

 1: open (DATA, "data.txt") || die "cannot open data.txt .. $!\n"; 
 2: while (<DATA>) {
 3:     chomp;
 4:     ($bangou, $tokuten) = split;
 5:     if ($tokuten =~ /--/) { print $bangou, "\n"; }
 6: }
 7: close DATA;





作業2

受験した学生の得点の平均値を計算するスクリプト(nm305.pl)を作りましょう。 小数点以下は四捨五入してください。 以下のスクリプトの抜けているところを埋めてみましょう。

ヒント
 1: open (DATA, "data.txt") || die "cannot open data.txt .. $!\n"; 
 2: $goukei = 0;
 3: $ninzuu = 0;
 4: while (<DATA>) {
 5:     chomp;
 6:     ($bangou, $tokuten) = split;
 7:     if ($tokuten =~ /\d+/) {
 8:         $goukei            ;
 9:         $ninzuu  ;
10:     }
11: }
12: close DATA;
13: print int(              + 0.5), "\n";

課題をどのように解いたか、「すべて自分でやりました」「M03-XXXの人に少しヒントを貰いました」 「M03-XXX, M03-YYY, M03-ZZZの3人と協力しました」などをはっきり書いて下さい。