パソコン活用研究5番街(Visual Basic、Excel(VBA)、BASIC プログラミング研究)

ファイルの操作(シーケンシャルファイル その1)

ファイル操作がこなせるようになれば、BASICも初級レベル卒業ではないでしょうか。
ゲームではあまりファイル操作をすることはありませんが、実用プログラム(Basicで実用プログラムができるの?という疑問が聞こえてきそうですが)では、ファイル操作がこなせないと話にならないでしょう。

ファイルは大きくわけてシーケンシャルファイルランダムファイルに大別されます。
(※)ランダムファイルについては「ファイル操作(ランダムファイル)」で解説しています。
   C言語のファイル操作は「低水準入出力関数」「ファイル入出力関数」で解説しています。
   Visual Basicでのバイナリファイルの操作は「Binaryファイルの入出力(読込)」「Binaryファイルの入出力(書込)」で解説

使い分けの目安:
・シーケンシャルファイル…データを頭から順に記録・再生する方式。名簿への追記など、順番に読み書きする用途に向いています。
・ランダムファイル…任意の位置へ直接データを読み書きできる方式。特定のレコードだけを書き換えたい場合に向いています。

まずは扱いやすいシーケンシャルファイルの取り扱いからとりあげてみたいと思います。

目次

1. ファイルのオープン

(1)ファイルのオープンとは

例えば、生徒の氏名とテストの点を記入したファイル rank.txt があるとしましょう。 このファイルからの読み込み、あるいは新たな生徒の点数の書き込みをしたい場合、このファイルを扱えるようにしなくてはなりません。 このファイルを扱えるようにする操作を「ファイルをオープンする」といいます。

ファイルをオープンするには、OPEN命令で、どのファイルを扱いたいのか(ファイルディスクリプタ)、読み込みたいのか書き込みたいのか(モードの指定)、ファイル入出力にどのバッファを使うのか(ファイル番号の指定)を指定します。

用語補足:
ファイルディスクリプタ…とっつきにくい名前ですが、まずは「ファイル名」と考えれば十分です(後述のとおり応用的にはデバイス名も指定できますが、最初は読み飛ばしてOKです)。
バッファ…データをファイルへ書き込む前、またはファイルから読み込んだ後に、一時的にためておくメモリ上の作業領域のことです。詳細は別記事「ファイルバッファ」で解説しています。この記事の範囲では「ファイルとやり取りするための一時置き場」程度の理解で問題ありません。

いちどきに複数のファイルを開いて使用することがありますが、どのファイルに対して読み書きするのかわかりやすくするために、オープンしたファイルにはファイル番号をつけて管理します。 以後、ファイルへの読み書きの時には、いちいちファイル名を書いたりせず、全てファイル番号でどのファイルへの読み書きかを指定します。

(2)OPEN文

ファイルをオープンする命令が OPEN です。このOPEN文もBasicの種類によって少しずつ書式や仕様が異なる厄介な命令です。

とりあえず、N88Basic、N88互換Basic for Win の OPEN文についてとりあげてみます。99BasicやQBasicでもだいたい同じです。
書式は次のとおりです。

OPEN "ファイルディスクリプタ" FOR モード AS #ファイル番号

例えば rank.txt を入力モードでオープンし、ファイル番号を1にするなら:

OPEN "rank.txt" FOR INPUT AS #1

先ほどは、ファイルディスクリプタ≒ファイル名、と言いましたが、細かいことを言うと、ファイルディスクリプタには、RS232C回線(COM:)、キーボード(KYBD:)、スクリーン(SCRN:)、プリンタ(LPT:)なども記述できます。 これによって、ユーザーの入力したデータをスクリーンに出すかプリンタに打ち出すか切り替えたり、といったことができます。詳細は別の機会に取り上げたいと思います。

モードはシーケンシャルファイルの場合、以下の3つになります。

モード意味
INPUT 入力モード。既存のファイルから入力を行います。指定したファイルが存在しないとエラー "File not Found" になります。
OUTPUT 出力モード。新しくファイルを作成し出力します。同一ファイル名があった場合は、既存のファイルは削除されます。
APPEND 追加出力モード。既存ファイルの終わりから追加出力を行います。N88Basicの場合は指定したファイルがないと"File not Found"エラーになりますが、N88互換Basic for Winの場合は新しくファイルを作成するようです(OUTPUTモードと同じ働きになります)。

99Basic、QBasicでは上記の書式・仕様はそのままあてはまりますが、少し追加の機能があります。
99Basicでは、モードの指定でOUTPUT、INPUT、APPENDが併記できたりします。
QBasicでは、モードにBinaryモードがあり、またネットワーク環境で使用する場合に記述するACCESS、LOCKというようなオプションがあります。詳しくはそれぞれの仕様書を確認して下さい。

F-Basicでは、若干書式が異なり:

OPEN "モード", #ファイル番号, "ファイルディスクリプタ"

モード指定は以下の通りです。

指定文字意味
I入力モード
O出力モード。同一ファイル名が既に存在すると、エラーになります。
A追加出力モード

2. INPUT #, PRINT #

ファイルからのデータ読み込み・書き込みには、INPUT #PRINT #を使います。
#でファイル番号を指定する以外は、通常のINPUT、PRINTと同じです。例えば:

INPUT #1, A, B, C
PRINT #2, A$; ","; B$; ","; C$
用語補足:変数名の末尾に $ がついているもの(A$など)は「文字列型の変数」であることを表します。数値変数(Aなど)とは区別されるので、コード中に$が出てきたら「これは文字列だ」と読んでください。

さて、ここが重要なポイントですが、ファイルの中で各データは区切り記号で区切られています(Basicによって使用される区切り記号が微妙に違うので気をつけて下さい)。 データを正しく書き込み、それを正しく読み込むには、データを適切に区切って書き込み(PRINT #)、読み込む時のデータの変数の型・個数を正しく記述(INPUT #)する必要があります。 ここで、区切り記号の入れ方や、読み込み時の変数の型・個数をミスすると、正しいデータの入出力ができません。場合によってはエラーでプログラムが中断します。

ですので、各BASICのINPUT #、PRINT #の仕様ははっきりと把握しておいて下さい。以下に代表的なBASICの場合について仕様を記載します。

N88Basicの場合

N88Basicの区切り記号は、数値データの区切り記号としては空白、コンマ、キャリッジリターン(CHR$(13))が使用され、文字列の区切りにはコンマ、またはキャリッジリターンが使用されています。

PRINT #で数値を出力する場合:

A=123: B=456: C=-789
PRINT #1, A; B; C

のようにセミコロンで区切ります。そうすると、ファイルの中にデータは1個の空白で区切られて、次のように書き込まれます。

123 456 -789

このデータを読み込む時は:

INPUT #2, A, B, C

とすると、Aには123、Bには456、Cには-789が読み込まれます。

出力するデータが文字列の場合には、ちょっと複雑です。空白は文字列の区切り記号として認識されないので、データとデータの間にコンマを区切り記号として入れてやる必要があります。例えば:

A$="CAMERA": B$="9600"
PRINT #1, A$; ","; B$

この場合、ファイルの中には次のように書き込まれます。

CAMERA,9600

これを:

PRINT #1, A$; B$

と記述すると、ファイルには CAMERA9600 と書き込まれてしまい、ひとつの文字列になってしまいます。

更に注意が必要なのは、文字列それ自身の中に、コンマやキャリッジリターンなどの区切り記号を含む場合です。

A$="CAMERA, AUTOMATIC": B$="9600"
PRINT #1, A$; ","; B$

とすると、ファイルには次のように書き込まれます。

CAMERA, AUTOMATIC,9600
失敗例:これを次のコードで読み込むとどうなるでしょうか。
INPUT #2, A$, B$
文字列自体の中にあったコンマがデータの区切りと誤認識されてしまうため、A$="CAMERA"B$=" AUTOMATIC"となり、"9600"は読み込まれずに残ってしまいます。区切り記号のミスは、こうした形で静かにデータを壊すので要注意です。

このような場合は、ダブルクォーテーション(CHR$(34))で文字列を囲んで出力する必要があります。例えば:

PRINT #1, CHR$(34); A$; CHR$(34); ","; CHR$(34); B$; CHR$(34)

としてやると、ファイルには次のように書き込まれます。

"CAMERA, AUTOMATIC","9600"

こうすれば、INPUT #で読み込む時に、ダブルクォーテーションで囲まれた部分をひとつの文字列として読み込みますので、正しく読み込みができるようになります。

F-Basicの場合

F-Basicでは、コンマ、コロン、キャリッジリターン、ラインフィードがファイルの中でデータの区切り記号になります。 また、PRINT#1INPUT#1のように#をPRINTやINPUTにくっつけて記述します。 またPRINT#で文字列を出力する場合、自動的にコンマを出力する仕様になっているので、N88Basicのように、コンマを自分で記述してやる必要がありません。

ここら辺はBASICによって微妙に仕様が違うところなのでご注意下さい。99BasicやQBasicはN88Basicに近いような気がしますが、詳細な仕様書がないので各自ご自分で試してみて下さい。 またN88互換BASIC for Winのver2.15では、PRINT #1, N$; ","; Sのように文字変数、数値変数と出力した時に、後ろの数値変数が前の区切り記号を消すような形で出力される(これはバグかもしれません)クセがありますので、PRINT #1, N$; " , "; Sのように区切り記号に余計な空白を追加してやるなどの工夫が必要です。

3. ファイルのクローズ

OPENにより開いたファイルを閉じるのがCLOSEです。書式はシンプルで、以下の通りです。

CLOSE #ファイル番号

ちなみに、#ファイル番号を省略すると、たいていのBASICでは、開いているファイルを全て閉じる、という動作になります。

書式はいたって簡単ですが、ファイルのクローズは重要な操作です。ファイルのクローズは、よく本を閉じる動作に譬えて説明されますが、実際はもっと重要な操作で、ファイルをクローズすることにより、以下が実行されます。

これにより、閉じたファイルへの読み書きができなくなりますので、うっかりファイルに誤った書き込みをしてしまうということを防止できます。プログラムミスでファイルをCLOSEし忘れて、おかしなデータを書き込んでしまう、なんてことが起こったらたいへんです。まあ、継ぎ足し継ぎ足しのタコ足のプログラムでもないかぎりあまり起こらないと思いますが(でも、まれにはあるかもしれません)。

よくあるのは、プログラムミスでファイルをクローズしないでプログラムを終了したり、その他のクラッシュでプログラムが異常終了して、ファイルをクローズしないまま、プログラムが終了してしまうケースです。 この場合、ファイルはオープンのままなので、もう一度同じプログラムを再起動して、そのファイルをオープンしようとしても、「そのファイルは既に開かれています」といったエラーが出てプログラムが停止してしまいます。
開いたファイルは必ずクローズするように気をつけましょう。またエラー発生時に、ON ERROR GOSUB等でエラートラップに飛ばした時に、場合によってはCLOSEでファイルを全部クローズするような対策をしておくことも必要かもしれません。

用語補足:文中に出てくる ON ERROR GOSUB(エラートラップ)とは、プログラム実行中にエラーが発生した際、指定した処理へ強制的に処理を移す仕組みのことです。これを使うと、エラー発生時に「開いているファイルを全部CLOSEしてから終了する」といった後始末の処理を書くことができます。詳細な書き方は別記事で扱う予定です。

同時にオープンできるファイルの数には制限があります(メモリーの大きさによりますが、通常8個とか16個とかでしょう)。大きなプログラムで多数のファイルを開く必要がある場合は、不要になったファイルはクローズして、そのファイル用に使っていたファイル番号を開けてやる必要があります。

バッファの仕組みについては差しあたって知らなくてもプログラミング上困らないと思いますが、知っていて損はないでしょう。バッファについては別記事「ファイルバッファ」をご参照下さい。

BASICによって差異がありますが、他の命令文でもファイルのクローズが実施されることがあります。

BASICファイルを自動的にクローズする命令
F-BASICRUN, LOAD, END
N88BASICRUN, LOAD, END, NEW

4. 数値の入出力(サンプルプログラム)

ひととおり基本的なところを書いてきましたが、ファイルの入出力も自分でプログラムを記述して体に覚えさせないとなかなか身につかないので、いろいろ試してみてください。

10°毎のSIN、COSの値をファイルに書き込み、それを読み出して表示するプログラムです。コードの各行に処理内容のコメントを添えていますので、参考にしてください。

10 '**** ファイルへの書き込み ****
20 close                              ' 念のため、前回開きっぱなしのファイルがあれば全て閉じておく
30 dim s(18), c(18)                   ' sin, cosの値を格納する配列を用意
40 open "sin-cos.txt" for output as #1 ' 書き込み用にファイルをオープン(ファイル番号1)
50 for d=0 to 180 step 10             ' 角度dを0°から180°まで10°刻みでループ
60 s(n)=sin(3.14/180*d): c(n)=cos(3.14/180*d)  ' 度をラジアンに変換してsin,cosを計算
70 print #1, s(n),c(n)                ' 計算結果をファイルに書き込み
80 n=n+1                              ' 配列の添字を1つ進める(nは未初期化なので自動的に0からスタート)
90 next
100 close #1                          ' 書き込みが終わったらクローズ(バッファの内容がファイルに確定される)
110 '
120 '**** ファイル読み込み ****
130 '
140 open "sin-cos.txt" for input as #1 ' 今度は読み込み用にオープン
150 for i=0 to 18
160 input #1, a,b                     ' 1行ずつsinとcosの値を読み込む
170 print "sin ";a,"cos ";b            ' 画面に表示
180 next
190 close #1                          ' 読み込みが終わったらクローズ
試してみよう:あえて sin-cos.txt が存在しない状態で140行目以降(読み込み部分)だけを実行すると、記事前半で説明した "File not Found" エラーを実際に確認できます。エラーメッセージとファイルの状態がどう対応しているか、体験しておくと理解が深まります。

実行例

sin-cos.txtの書き込み・読み込みを実行した際のコンソール出力例

TOP PAGE > Basic活用研究目次