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

4. ファイルからの入力とsplit:簡単なデータ処理がすぐできる

ファイルからデータを読み込む

前のページでは,Perl のプログラムの出力を,DOS窓のリダイレクト機能を 使ってファイルに書き込んでみました. このページでは,DOS窓でプログラムを起動するときに指定した 既存ファイルを読み込む方法を説明します (※プログラム中で名前を指定してファイルを作ったり読んだりする方法は, もっとあとで出てきます).

#  コマンドラインで指定したファイルから一行ずつ読み込み,
#  行番号をつけて画面に表示する.

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

while ($line = <>)  {  #  一行読んで,$line という変数に代入.詳しくは下記.
    print $no, "\t", $line;  #  行番号,タブ,行の内容.
    ++$no;             #  行番号カウンタを増やす.
}

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

このなかで,while ($line = <>) {} というところがポイントですが, その説明の前に,このプログラムの使い方を示します.

最初の行のコメントに,コマンドラインという言葉が出てきます. DOS窓(ないしは仮想端末)では, perl test.pl のように命令を 一行で書いて,Enterキーを押して命令を実行させますが, この一行をコマンドラインと呼びます. プログラムファイルの名前が read_line.pl, 読み込みたいファイルの名前が test_data.txt だとしたら,DOS窓から つぎのようにコマンドラインを入力して実行します.

 perl read_line.pl test_data.txt

すると,test_data.txt の各行が行番号付きで表示されます. そして,最後に全部で何行あったかが表示されます.

では,while ($line = <>) の説明です. while 文についてはすでに繰り返しのところで説明しました. ()の中の論理式が入るはずのところには,<> と書かれています. <>入力演算子と 呼ばれるものです.ファイルからデータを読み込むときは,そのファイル専用の 窓口(ファイルハンドルと呼ぶ)を用意して, その窓口の名前を <> の中に書くのですが, その詳しい説明はもっとあとででてきます. 窓口名を指定しない <> は,DOS窓で Perl のプログラム名の うしろに書いたファイル(上の例だと test_data.txt)からデータを読み込むための 窓口の役目をします. 少々ややこしいですが, 「代入演算子の右側に <> があったら,DOS窓で指定したファイルから 一行読んで,代入演算子の左辺にある変数に代入する」 ということだけ理解してください.

※なお,ファイル名の指定を忘れると,フリーズしたようになりますが, ファイルのかわりにキーボードからの入力を期待して待ち状態になっているだけです. キーボードのコントロールキーを押しながら 'C' を押すと,プログラムを終了させる ことができます.この強制終了ワザは,プログラムのミスで暴走させてしまった (たとえば無限ループ)ときにも使えます.

代入演算子を含む式全体の値を評価しようとすると,代入された値が式の値となります. たとえば $y = ($x = 3); という文では, ($x = 3) という式の値すなわち 3 が $y に代入されます.

while () のカッコの中は,論理式として真か偽か評価されます. $line = <> という式の場合,ファイルから読み込まれた文字列が 式の値となります. 本来,文字列や数値には真も偽もないのですが,無理に真か偽か評価しようとすると, 空の文字列や数値のゼロは偽,未定義値も偽,それ以外の任意の文字列や数値は真と 評価されます.

処理の流れの制御のページ で,しつこく「論理式と仮に呼んでいるけど,実はどんな式も真か偽か評価できる」と 書いてたのは,このことでした.

$line = <> では,末尾に改行コードがついた文字列が読み込まれます. たとえ空白の一行であっても,$line は空文字列にはなりません. 末尾の改行コードが代入されます. 空文字列ではないかぎり,真か偽かといわれたら真なので, while () のうしろの {} で囲まれた一連の処理を実行します.

ファイルをずっと読んでいって,末尾まで読み切ってしまうと,さらに $line = <> で読もうとしても読めません.このとき,$line には 前に「変数」のところで出てきた未定義値という特別な値がセットされます. 未定義値は,真か偽かと言えば偽なので,ここで while の繰り返しを終了します.

…と,なかなか面倒な説明でしたが,

while ($line = <>)  {
    処理
}

と書けば,プログラムのうしろに指定したファイルから一行ずつ読み込んで, そのたびに処理が実行されるんだ,と覚えてしまえばとりあえず用は足ります.


データファイルの一行を個々のデータに切り分ける

表計算ソフト(たとえば Excel のような)で,タブ区切りや空白(スペース) 区切り形式でファイルを保存すると,一行ごとに複数のデータが空白やタブで 区切られて並んだデータファイルができます. いろいろな測定機器やデータロガーが送ってくるデータも,しばしばこういう 形式です. 人間がエディターなどで一生懸命データを入力するときも, 同様な形式のファイルをつくることが多いでしょう.

このようなデータファイルを読み込んで,そのデータを処理するには, まずは一行分の文字列を個別のデータに分割する必要があります. そのためによく使われるのは,split という関数です. 名前のとおり,この関数は文字列をばらばらに切り分けます.

こんなデータがあったとします.

Atsushi 177.0   75.0
Hide    175.0   68.0
Yutaka  180.0   78.0
Koji    182.0   74.0
Shinji  175.0   74.0
Mitsuo  173.0   68.0

各行に,名前,身長,体重が並んでいます(このデータはフィクションです). このデータから,各人の BMI (Body Mass Index, 体重/身長(m)2)を 計算してみます.

while ($line = <>)  {
    chomp $line;  #  $line の末尾の改行コードを削除する.

    # 空白文字で分割して作ったリストを $name, $height, $weightに代入.
    ($name, $height, $weight) = split (/\s+/, $line);  

    $bmi = $weight / ($height / 100.0) ** 2.0;  # BMI を計算.身長の単位に注意.
    printf "%s\t%.1f\n", $name, $bmi;  # 名前とBMI (小数点以下1ケタまで)を出力
}

$line = <>; で1行分のデータを$line に読み込んだあと, chomp という関数を使っています.これは,文字列の最後に改行コードが あったらこれを取り除く,という働きをする関数です.データを表示するときに 予期せず改行されちゃったりすると面倒なので,ファイルから読み込んだデータ はまず chomp しておくとよいでしょう.

split 関数のところを説明するまえに,リスト というものを簡単に説明します. リストは,任意の個数の変数やリテラル(プログラム中にじか書きした 文字列や数値)が並べられたものです. プログラム中に書くときは,変数名やリテラルをコンマ ','で区切って書いて, 全体をカッコ () で囲みます. 上のプログラムでは,($name, $height, $weight) が3つの変数を要素として持つ リストです.

代入演算子の左右にリストがあると,右辺のリストの最初の要素が 左辺のリストの最初の要素に代入され,右辺の2番めの要素が左辺の2番めの 要素に代入され…,という代入が一度に行われます. たとえば ($x, $y, $z) = (1, "two", 3); という文が実行されると, $x の値は 1,$y の値は "two",$z の値は 3 になります.

話を split に戻します.この関数は,最初の引数(ひきすう)で指定したものを 区切りとして2番めの引数で指定した文字列を分割し, その結果をリストとして返します.だから, ($name, $height, $weight) = split /\s+/, $line; という文はリストからリストへの代入になり,分割された文字列の最初の 要素が $name に,2番めの要素が $height に,3番めの要素が $weight に 代入されます.

うえの段落の説明でごまかしたところが一ヶ所あります. 「最初の引数(ひきすう)で指定したもの」の「もの」ってなんだ? これは「正規表現を使って書かれたパターン」です. 正規表現は,"アルファベットが1文字以上"とか, "文字列の先頭に数字2文字,そのあとにaかb"とか, "空白かタブが一個以上連続"とかいった文字の並び方の特徴を記述する 表現方法です.これじゃなんだかよく分かりませんね. 正規表現については別のページでもっと詳しく説明します.

とりあえず,上のプログラム例で使われている /\s+/ を理解するのに 必要なことだけ.

というわけで,split (/\s+/, $line) と書くと,変数 $line に記録されている 文字列を,ひとつ以上空白かタブが並んでいるところで区切れ, という意味になります.区切って生成される個々の短い文字列には, 区切りのパターン(ここでは空白かタブ)は含まれません.

…と,これまた長い説明でした.実用上は, 「タブ区切りや空白区切りのテキストファイルを一行ずつ読んで,各行の データをばらして使うには,

while ($line = <>)  {
    chomp $line;
    (変数のリスト) = split (/\s+/, $line);  
}

と書けばよい」ということが分かればけっこうです.

データファイル中でのデータの区切りがカンマの場合には, // のあいだにカンマをそのまま書いて split (/,/, $line); とします. また,カンマのあとに空白があるかもしれない場合は,カンマのうしろに \s* を加えて split (/,\s*/, $line); とします. + は直前の文字が1個以上連続しているという意味でしたが, * は直前の文字が0個以上連続していることを意味します.

<練習>

※表計算ソフトを使ってない人なら,手作業でデータファイルを作って処理してみましょう.

BMI の例は,表計算ソフトのなかでも一気にできてしまう処理でした. 次は,一気にはできない作業をプログラムで自動化する例を紹介します. だいぶ長くなったので,続きは次のページにて.



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