6.1 ファイルの手軽な読み書き
6.1.1 変数をバイナリで読み書きする:save, load
MATLAB で作成した変数はワークスペースに保存されます. MATLAB のウィンドウ右側上部にある 「ワークスペースブラウザ」には, ワークスペースに保存されている変数の一覧が示されています.
この節で学習する save 関数はワークスペースにある任意の変数をファイルに保存します. これと対になる load 関数は,ファイルからワークスペースに変数を読み込みます.
例題 ex6101 に示されているように, ワークスペースにある変数 A および B を ex6101.dat というファイル名で保存するには,
save('ex6101.dat', 'A', 'B')
とします(この書き方は,例題 ex6101 と異なり,ファイル形式の指定を省略しています). 変数 A だけを保存するなら
save('ex6101.dat', 'A')
となります.
ファイルの保存先はワーキングディレクトリです.
ファイル名と変数名は文字列で指定します. つまり,一重引用符(')で囲みます(第2章2.5.1節参照). 二重引用符で囲んでもいいですが,一重引用符の使用を勧めます.
ファイルの拡張子は自由につけられますが,奇抜なものは避けましょう. 例題 ex6101 では拡張子を dat としています. データファイルであることを連想させる拡張子です. この例題ではファイル形式を MAT とする指定を行っていますが, 指定を省略すると,ファイル形式は MATLAB 独自のバイナリ形式である MAT になります. よって,拡張子を dat でなく mat にしてもよいでしょう.
例題 ex6101 では,save 関数の最後の引数として,
ファイル形式を '-mat'
と指定しています.
すぐ上で述べたように,これは MATLAB 独自のバイナリ形式です.
ファイル形式をこのように指定すると,ファイル名および拡張子にかかわらず,
MAT ファイルとして保存されます.
ファイル形式として指定できるのは MAT,あるいは,テキスト形式の ASCII です(6.1.2節参照).
MAT 形式のデータファイルは,MATLA B以外で利用が難しいという欠点はありますが, 通常はこの形式を利用する方がよいでしょう. 次節を学習するとわかるように,テキスト(ASCII)形式での保存にはいろいろな制約があります.
例題 ex6101 では,save 関数で保存した変数 A および B を, load 関数で読み込んでいます. 保存した変数名がわからなければ,単に
load('ex6101.dat', '-mat')
とすればいいです.保存されていたすべての変数(この例題では A と B)が読み込まれます.
この例のように,拡張子が mat ではない MAT ファイルの場合,
ファイル形式の指定である '-mat'
は省略できません.
MAT 形式のファイルとして保存されている変数名を調べたければ, whos 関数(第2章2.5.1節参照)を利用できます. 例題 ex6101 の場合,
whos('-file', 'ex6101.dat')
とします.下のように,ex6101.dat に保存されている変数の情報が表示されます.
MAT ファイルに保存されている複数の変数のうち, 一部の変数だけ読み込むこともできます.たとえば, ex6101.dat に保存されている変数 A および B のうち,A だけを読み込みたければ,
load('ex6101.dat', 'A', '-mat')
とします.
テキストでは説明されていませんが, ワークスペースの変数を MAT ファイルに保存するのに GUI を利用できます. 例題 ex6101 の変数 A および B がワークスペースにあるとき, 「ホーム」タブの「変数」カテゴリにある「ワークスペースの保存」をマウスでクリックしてください(下図).
「名前を付けて保存」というウィンドウが現れますので(下図), ファイル名を付けて [保存] ボタンをクリックしてください. ファイル名はデフォルトで matlab.mat となっています.
6.1.2 変数をテキストで読み書きする:save, load
ワークスペースの変数をテキスト形式のファイルに保存するには,
save 関数でのファイル形式の指定を '-ascii'
とします.
たとえば,前節の例題 ex6101 で作成した変数 A および B をテキスト形式で保存するには
save('ex6101.dat', 'A', 'B', '-ascii')
とします.
テキスト形式のファイルは type 関数で内容を調べることができます. 例題 ex6101 で作成した変数 A および B をテキスト形式のファイル ex6101.dat に保存したあと, コマンドウィンドウで
type ex6101.dat
とキーインしてください.このファイルの内容がコマンドウィンドウに表示されます(下図).
この表示からわかるように,複数の変数をテキスト形式で保存すると, それらはひとつにまとめられてしまいます. もとの変数名情報は保存されません.
この例ではもうひとつ困ったことが生じます. 変数 A と B に保存された行列の列数が異なるため, 作成したテキスト形式のデータファイルを load 関数で読み込むことができません. 下図のようにエラーとなります. 「入力は、各行が同じ列数である数値データを含む MAT ファイルまたは ASCII ファイルでなければなりません。」 というエラーメッセージが表示されます.
例題 ex6102a では,変数 X および Y を別々のデータファイルに保存しています. それぞれ,ファイル形式はテキスト(ASCII)で, ex6102aX.dat および ex6102aY.dat というファイル名です.
変数をテキスト形式で保存すると,もとの変数名の情報は失われてしまいます.そのため, ファイルに保存された変数を load 関数で読み込むときには(例題 ex6102a のプログラム6行目および7行目), 例題 ex6101 のプログラムと異なり,もとの変数名の指定は行いません. ファイル名と同じ名前の変数(ここでは,ex6102aX および ex6102aY)が生成されます.
例題 ex6201b で文字列(文字配列)を作成するのに用いている char 関数は第2章の2.5.1節で学習しました. この関数の引数に複数の文字列を並べると, これら文字列を各行とした行列が作成されます. 行列が作成されるので文字列の区切りをセミコロン(;)にしてしまいそうですが, 関数の引数なので,コンマで区切って並べます.
第2章の2.5.3 節で学習したように,char 関数はASCIIコード(数値)から文字への変換にも使用されます. 例題 ex6201b のプログラム6行目では,変数 ex6102b に保存されているASCIIコードを, char 関数で文字に変換しています.
6.2 テキストファイルの読み書き
6.2.1 ファイルに書き込む:fprintf, fopen, fclose
テキスト p.186 中ほどの「fprintf の引数に書かれるオプション permission」は 「fopen の引数に書かれるオプション permission」の誤植です.
ファイルにデータを書き込む一般的なやり方について, テキストでは本節の最初に「C 言語や Java 言語などと同様に」と説明されていますが, 「情報処理心理学実習」の受講者はおそらくこれらの言語を知らないと思います. 知らなくても問題ありません.
ファイルを開くという操作は新規ファイルの作成でも必要であることに注意してください. 例題 ex6201 では ex6201.txt というファイルを開きますが, この例題に初めて取り組むときには,このようなファイルはどこにも存在しないでしょう. つまりこのファイルは新規作成することになりますが, この場合にもファイルを開くという操作が必要となります. 新規作成の場合も含め,ファイルを開くには fopen 関数を使います. ファイルを開くとき,この関数は ファイル識別子(file identifier)という整数値を返します. この値は最後に fclose 関数でファイルを閉じるときに必要となりますので, 変数(例題 ex6201 では Fid)に保存しておきます.
ファイルを開くとき,そのファイルに対して ファイルアクセスタイプ(permission)を指定します. ファイルアクセスタイプとは,そのファイルに対してどのような操作を行うかを意味します. テキスト p.186 にファイルアクセスタイプの一覧が示されています. これは理解が難しいですね.以下に使い分けを説明します.
この一覧にあるファイルアクセスタイプのすべてに t という文字が含まれていることに注意してください. これは「テキスト(text)」モードという意味です. MATLAB では,ファイルを開くモードに テキストモード と バイナリモード があります. テキストファイルを開くときにはテキストモードで開きます. バイナリモードについては 6.3 1 節で学習します.
ファイルアクセスタイプ r は読み込み(read)を意味します. このタイプはすでに存在しているファイルを読み込み専用で開きます. ここで「読み込み専用」とは,ファイル内にあるデータを読み込むだけで, ファイルにデータを書き込むことはしないという意味です. ファイルが存在していない場合は開くことができず, ファイル識別子は -1 が返されます. ファイルを開くときに,fopen 関数の引数でファイルアクセスタイプが省略されると, このファイルアクセスタイプが指定されたものと見なされます.
ファイルアクセスタイプ w は書き込み(write)を意味します. 例題 ex6201 で行っているように,データを書き込むファイルを新規作成するときにこのタイプを指定します. 新規作成されたファイルは書き込み専用です. すでに同名のファイルが存在している場合,そのファイルは破棄されてしまいます. このタイプでは,すでに存在するファイルを開いて書き込みをすることはできませんので, 注意が必要です. 文書作成や表計算ソフトウェアの使用経験からすると, 「ファイルを開く」のであれば読み込みができているはずで, 書き込み専用とはどういうことかと思われるかもしれません. このアクセスタイプはデータを書き込むためのファイルを新規作成するのであり, ファイルからデータの読み込みはしないと理解してください.
ファイルアクセスタイプ a は追加書き込み(append)を意味します. すでに存在するファイルを開き,ファイルの末尾に追記を行います. ファイルが存在しない場合は新規に作成されます.
末尾にプラス(+)がつけられたファイルアクセスタイプは読み込みと書き込みの両方が可能です. たとえば,ファイルアクセスタイプを rt+ と指定すると, 読み込みに加えて書き込みができるようになります. このとき,r という指定は生きていますので, 開こうとしているファイルは存在している必要があります(新規作成はできない). ファイルアクセスタイプを wt+ と指定すると, 書き込みに加えて読み込みができるようになります. このとき,w という指定は生きていますので, 開こうとしているファイルが存在していなければ,新規作成されます.
ファイルアクセスタイプについての説明は以上です.
開いたファイルにデータを書き込むには fprintf 関数を使います. 例題 ex6201 では
fprintf(Fid, '%2d%5d\n', Y);
としています.第1引数(変数 Fid)はデータを書き込むファイル(ex6201.txt)のファイル識別子, 第3引数(変数 Y)はファイルに書き込むデータです. データは行列で与えられており,
です.
行列形式で与えられたデータは第1列から順に書き込まれていきます. すなわち,
という順序です.
データの書き込みを行うとき,書式(format)を fopen 関数の第2引数で指定します.
データは文字列として書き込まれるので,書式の指定は引用符(')で囲みます.
例題 ex6201 では '%2d%5d\n'
という指定がなされています.
これは
「2桁の幅を使った10進数の整数,5桁の幅を使った10進数の整数,改行」
という意味ですが,初めて見るときにはわかりにくいですね.
順に説明します.
例題 ex6201 を実行した後か,プログラム1行目の Y を作成した後で, コマンドウィンドウで
fprintf('%d', Y)
とキーインしてください.ファイル識別子なしで fprintf 関数を使うと, コマンドウィンドウにデータが書き出され, 下図の出力が得られます. データが出力された直後にプロンプト(>>)が表示されていますが, [Enter] キーを押せばいつもの位置に戻ります.
ここで用いた %d という書式指定は「10進数の整数」を意味します. 行列 Y で与えられたデータが,上述の順序で,コマンドウィンドウに表示されています. この書式指定での文字 d は 変換文字(conversion character)と呼ばれる文字のひとつです. テキスト p.187 に変換文字の一覧が与えられています.
次に,コマンドウィンドウで
fprintf('%2d', Y)
とキーインしてください.下図の出力が得られます.
ここで用いた %2d という書式指定は「2桁の幅を使った10進数の整数」を意味します. 数値それぞれの表示に2桁の幅が費やされていることがわかります. この書式指定での数値 2 は フィールド幅(field width)と呼ばれます.
次に,コマンドウィンドウで
fprintf('%2d%5d', Y)
とキーインしてください.下図の出力が得られます.
ここで用いた %2d%5d という書式指定は 「2桁の幅を使った10進数の整数,5桁の幅を使った10進数の整数」を意味します. 数値の表示幅は2桁と5桁が繰り返されています. 桁数がわかりにくければ,
fprintf('%02d%05d', Y)
とキーインしてください.出力は下図のようになります. 空白のかわりに 0 が表示されるので,桁数がわかりやすくなっています.
最後に,例題 ex6201 と同じく, コマンドウィンドウで
fprintf('%2d%5d\n', Y)
あるいは
fprintf('%02d%05d\n', Y)
とキーインしてください.下図の出力が得られます.
ここで用いた %2d%5d\n という書式指定は 「2桁の幅を使った10進数の整数,5桁の幅を使った10進数の整数,改行」を意味します. 2つの数値が2桁と5桁で表示された後,改行が入ります. 改行などいくつかの特殊文字は特別な方法で表記します(テキスト p.187-188 の一覧を参照). 改行はバックスラッシュに続けて n ですが, 日本語キーボードボードでは,バックスラッシュは円記号 \ に該当します.
例題 ex6201 のプログラムを実行したら, テキストファイルの内容を表示する type 関数で ex6201.txt の内容を確認しましょう.コマンドウィンドウで
type ex6201.txt
とキーインしてください.下図の出力が得られます. 変数 Y に保存されたデータが 「2桁の幅を使った10進数の整数,5桁の幅を使った10進数の整数,改行」 という書式でファイルに書き込まれたことがわかります.
重要な注意があります. 変数 Y に保存されていたデータには数値として意味があります. 実際,Y は2行5列の行列ですから,数学的な操作が可能です. 一方,このデータが書き込まれたファイルは単なるテキストファイルであって, ファイル中のデータに数値としての意味はありません. たとえば,ファイルに書き込まれた「1」は数値ではなく, 「あ」や「X」と同じく文字です.「数値」ではなく「数字」です.
例題 ex6201 では数値だけをファイルに書き込みましたが, ファイルに書き込まれるのは fprintf の第2引数に書かれた文字列ですので, 文字も書き込むことができます(実際のところ,数値も文字として書きこまれています). MATLAB のドキュメンテーションに例示されている, 以下のプログラムを実行してみてください. ここで,%4.2f という書式は, 「小数点以下2桁を含む,フィールド幅4桁の浮動小数点数(固定小数点表記)」 を意味します. もうひとつの %8.3f という書式も同様の意味です. 小数点以下のある実数を書き込むにはこの書式を用います.
A1 = [9.9, 9900];
A2 = [8.8, 7.7 ; 8800, 7700];
formatSpec = 'X is %4.2f meters or %8.3f mm\n';
fprintf(formatSpec, A1, A2)
コマンドウィンドウに以下の出力がなされます(スクリプト m ファイルの名前は untitled.m としています). もちろん,ファイルへの書き込みを行うようにプログラムを変更することは容易です.
6.2.2 ファイルから読み込む:fscanf
例題 ex6202 を実行する前に,type 関数で ex6201.txt の内容を確認してください. 例題 ex6201 での変数 Y の内容も確認しておくとよいでしょう.
ファイルへの書き込みを行うときと同様に,ファイルからのデータの読み込みでは, 最初に fopen 関数でファイルを開き,最後に fclose 関数でファイルを閉じます.
ファイルを開くときのファイルアクセスタイプは, テキストモード(text)での読み込み(read)を意味する rt とします.
開いたファイルからデータを読み込むには fscanf 関数を使います. 読み込んだデータは変数(例題 ex6202 では変数 Z)に保存します. 例題 ex6201 では
Z = fscanf(Fid, '%d%d', [2, inf])
としています.
第1引数は,fprintf 関数と同様に,開いたファイル(ex6201.txt)のファイル識別子です.
第2引数では読み込むデータの書式を指定します.
データは上の行から順に行単位で読み込まれていきます.
各行には2つの整数値がありますので,例題 ex6202 での書式は2つの整数値を意味する
%d%d
となっています(実際には,%d ひとつだけでも動作します).
例題 ex6201 でファイルにデータを書き込むときにはフィールド幅を指定しましたが,
読み込むときには不要です.
テキストでも注意されているように, ファイル内のデータは上の行から順に行単位で読み込まれますが, これを変数(例題 ex6202 では変数 Z)に保存するときには, 読み込まれたデータは列方向に保存されます. 下図は,例題 ex6202 のプログラム2行目を
Z = fscanf(Fid, '%d%d')
に変更し,4行目を削除して実行したものです. 上の行から順に行単位で読み込まれたデータが1列に保存されていることがわかります.
ファイルから読み込んだデータを行列の形式で保存するには,
fscanf 関数の第3引数で,行列の行数 m と列数 n を
[m, n]
という形式で指定します.
このとき,列数を数値として明示せずに,行数だけを指定することができます.
こうした指定は [m, Inf]
と書きます.
例題 ex6202 では行数だけを 2 と指定しています.
列数だけを指定することはできません.
読み込んだデータを行数 m の行列にするとき, 要素がどのように並べられるかを理解してください. 次のように考えるとよいでしょう. 最初に,上図のようにデータを1列に並べます. 次に,上から m 個ずつ区切り,区切られたブロックを横方向に並べます. 例題 ex6202 のプログラム2行目最後にあるセミコロンを外し, 変数 Z の内容を表示するとよいでしょう. 下図では,変数 Z の内容である2行5列の行列と, プログラム4行目でそれを転置した5行2列の行列が示されています.
ファイル ex6201.txt の中では,データは5行2列で保存されていました. これを読み込んだ行列 Z は2行5列ですので, ファイルの中のデータと同じ形にするには行列 Z を転置します(プログラム4行目). しかし,もともと例題 ex6201 で ex6201.txt に保存したデータ Y は2行5列の行列でした. よって,行列 Z はもとのデータ Y と同じ形式になっていますので, 転置する必要はないかもしれません.
前節で述べたのと同様の重要な注意があります. データが保存されていた ex6201.txt はテキストファイルですから, そこにあるすべては文字です. たとえば,「1」は数字であって,数値としての意味は持っていません. このファイルの内容を fscanf 関数で読み込むときに変換の指定を %d とすると, 読み込まれた文字が10進数の整数値として解釈されます. したがって,行列 Z のデータは数値としての意味を持っています.
テキストファイルからデータを読み込むときに, 変換の指定を %s にすると,ファイル中のデータは文字として解釈されます. 空白は無視されます. 例題 ex6202 のプログラム2行目を
Z = fscanf(Fid, '%s');
に変更して,プログラムを実行してみてください. 結果は下図のようになります(スクリプト m ファイルの名前は ex6202s.m としました). 文字配列を意味するシングルクォーテーションで数字が囲まれていますから, テキストファイル中にあった数字が,そのまま文字として解釈されていることがわかります.
6.2.3 ファイルから文字列を探す:fgetl, strfind, feof
例題 ex6203 では,例題 ex6201 で作成したテキストファイル ex6201.txt の内容を1行ずつ読み込み, その行に 1 という文字が含まれているか調べます. テキストファイルの末端に到達したら検索は終了です.
最初に,文字列を探すファイルを fopen 関数で開きます. ファイルアクセスタイプはテキストファイルの読み取り(rt)とします. ファイル識別子を変数 Fid に保存します.
このとき,ファイルの先頭行を指している「ポインタ(pointer)」があることをイメージしてください. ファイル識別子を引数として受け取った fgetl 関数は.ポインタが指している行を文字列(文字配列)として読み込みます. 読み込みが終わると,ポインタは次の行に移動します.
この動作をコマンドウィンドウで確認してみましょう. コマンドウィンドウで,例題 ex6302 の1行目を末尾のセミコロンなしでキーインします. 続いて,
fgetl(Fid)
とキーインしてください.下図のような出力が得られます. テキストファイルの1行目が読み込まれたことがわかります. テキストファイルから読み込まれたのは文字列(文字配列)ですから, シングルクォーテーションで囲まれています.
ファイルの先頭行を指していたポインタは,fgetl 関数が実行されたことにより, 次の行に移動しました. もう一度
fgetl(Fid)
とキーインしてください.下図のような出力が得られます. テキストファイルの2行目が読み込まれたことがわかります.
このように,fgetl 関数はポインタが指している行を文字列(文字配列)として読み込みます. 読み込みが終わるとポインタは次の行に移動します. このまま,fgetl 関数で行を次々に読み込んでいきましょう.
どのファイルも, 最後には 終端指定子(end-of-file indicator)と呼ばれる特別な記号が記録されています. 最終行の読み込みが終わると,ポインタは終端指定子の位置に移動します. 終端指定子は EOF と表記します.
ポインタが終端指定子を指しているときに fgetl 関数を実行すると, -1 という値が返されます.
ポインタが終端指定子の位置まで来ると,fgetl 関数を実行してもポインタの位置は動きません. したがって,fgetl 関数の実行を繰り返しても,-1 という値が返され続けます.
ここまでのことが理解できたら.fclose 関数でファイルを閉じておきましょう.
例題 ex6203 では,テキストファイル ex6201.txt から, ポインタが指している1行を読み込み, 1 という文字が含まれているか調べ, ポインタを次の行に進めるという処理を繰り返します. この繰り返しは while ループで制御されています. ポインタが終端指定子 EOF を指したら繰り返しは終了です. ポインタが EOF を指しているかどうかの判定には feof 関数を使います(プログラム2行目). この関数は,ポインタが EOF を指しているときには 1 を, それ以外では 0 を返します. したがって,この関数が返す値が 0 である限り, ループを繰り返せばよいということになります.
ループ中での処理を確認しましょう. テキストファイル ex6201.txt から fgetl 関数で読み取った1行を, 文字配列として変数 Line に保存します(プログラム3行目). この関数はポインタを次の行に進める役割も果たします. 次に,strfind 関数が, Line に保存された文字配列中から '1' を探します(プログラム4行目). 見つかった場合には,この関数はその位置をベクトルにまとめて返します. たとえば,テキストファイル ex6201.txt の1行目では, 2文字目と7文字目に 1 があるので, [2 7] というベクトルが返されます(1文字目と,3文字目から6文字目は空白です). 返されたベクトルは変数 Index に保存します. 探している文字列(ここでは '1')が見つからなかった場合には, strfind 関数は空のベクトル [ ] を返します.
例題 ex6203 のプログラム5行目では, 探している文字が見つかった個数を変数 N に保存しています. この個数は Index の要素数を length 関数で調べればわかります. たとえば,Index が [2 7] ならば,2つ見つかったということになります.
変数 N の値が 0 より大きい場合,つまり,探した文字列が見つかった場合,
見つかった個数 N と,(変数 Line に保存されている)その行の内容を,
fprintf 関数でコマンドウィンドウに出力します.
ファイル識別子なしで fprintf 関数を使うと,コマンドウィンドウに出力されるのでした.
出力の書式指定は %d:%s
ですから,
「変数 N の内容を10進数の整数値,コロン(:),変数 Line の内容を文字列,改行」
という出力になります.
以上に述べた動作を確認するには, プログラム3行目から5行目の末尾にあるセミコロンを外して, プログラムを実行してみるとよいでしょう. これまでも行ってきたと思いますが, 処理のプロセスを出力することは,プログラムの動作を理解するための有効な学習方略です.
例題 ex6203 のプログラムは,探す文字列は固定('1'), この文字列を探すファイルも固定(ex6201.txt)ですね. 任意のテキストファイルに対して任意の文字配列を探すことのできる, このプログラムの関数バージョンを考えてみましょう. 例題 ex6203 のプログラムを少し書きなおすだけです. 以下に解答例を示します.関数名は ex6203fun としています.
function ex6203fun(filename, target)
Fid = fopen(filename, 'rt');
while feof(Fid) == 0
Line = fgetl(Fid);
Index = strfind(Line, target);
N = length(Index);
if N > 0
fprintf('%d:%s\n', N, Line);
end
end
fclose(Fid);
end
この関数の実行例は以下のようになります.