ファイル

前の章で、標準入力と標準出力ストリームは、ターミナル上で><|演算子を使用して、ファイルや他のプログラムにリダイレクトできることを学んだ。これらのツールは非常に強力だが、すべての状況に適しているわけではない。なぜなら、多くの場合、プログラムは入力から読み込み、出力に書き込むだけでタスクを完了できないからである。

例えば、学生の成績を扱うプログラムは、標準出力を使用してプログラムメニューを表示する場合がある。このようなプログラムでは、stdoutではなく、実際のファイルに学生の成績を書き込む必要がある。

この章では、ファイルシステム上のファイルからの読み込みとファイルへの書き込みについて説明する。

基本的な概念

ファイルは、std.stdioモジュールにあるFile構造体で表現される。構造体についてはまだ説明していないので、構造体の構文はとりあえずそのまま受け入れておいて。

コードサンプルに入る前に、ファイルに関する基本的な概念を確認しておこう。

プロデューサーとコンシューマー

あるプラットフォームで作成されたファイルは、他のプラットフォームではそのまま使用できない場合がある。単にファイルを開いてデータを書き込むだけでは、そのデータが消費者の側で利用可能になるわけではない場合がある。データの生産者と消費者は、ファイル内のデータのフォーマットについてあらかじめ合意しておかなければならない。例えば、生産者が学生のIDと名前を特定の順序で記録した場合、消費者はそのデータを同じ順序で読み込む必要がある。

さらに、以下のコード例では、ファイルの先頭にバイトオーダーマーク(BOM)が書き込まれていない。そのため、BOMを必要とするシステムでは、ファイルが互換性がない可能性がある。(BOMは、ファイル内の文字のUTFコード単位の配置順序を指定するものだ。)

アクセス権

ファイルシステムは、特定のアクセス権の下でファイルをプログラムに提示する。アクセス権は、データの整合性とパフォーマンスの両方にとって重要だ。

読み取りに関しては、複数のプログラムが同じファイルから読み取りを許可すると、プログラムが互いの読み取り操作を待たずに済むため、パフォーマンスが向上する。一方、書き込みに関しては、たとえ1つのプログラムだけがファイルに書き込みを行う場合でも、ファイルへの同時アクセスを禁止したほうがいい場合が多い。ファイルをロックすることで、オペレーティングシステムは、他のプログラムが部分的に書き込まれたファイルを読み込んだり、互いのデータを上書きしたりすることを防ぐことができる。

ファイルの開く

標準入力ストリーム`stdin`と標準出力ストリーム`stdout`は、プログラムが実行を開始する際に既に開かれている。これらは使用可能な状態になっている。

一方、通常のファイルは、ファイル名と必要なアクセス権を指定して、まず開く必要がある。以下の例でわかるように、Fileオブジェクトを作成するだけで、その名前で指定したファイルを開くことができる。

File file = File("student_records", "r");
D
ファイルの閉じ方

プログラムによって開かれたファイルは、そのファイルの使用が終了した際に必ず閉じなければならない。ほとんどのケースでは、ファイルを明示的に閉じる必要はない。Fileオブジェクトが自動的に終了すると、ファイルも自動的に閉じられる:

if (aCondition) {

    // ファイルオブジェクトが作成され、ここで使用されていると仮定する。
    // ...

} // ← 実際のファイルは、このスコープを離れると
  //   自動的に閉じる。明示的に閉じる必要はない。
D

一部のケースでは、異なるファイルにアクセスしたり、同じファイルに異なるアクセス権でアクセスしたりするために、ファイルオブジェクトを再開する必要がある。そのような場合は、ファイルを閉じてから再開する必要がある:

file.close();
file.open("student_records", "r");
D
ファイルへの書き込みと読み取り

ファイルは文字ストリームであるため、入力および出力関数writelnreadfなどは、ファイルに対してもまったく同じように使用できる。唯一の違いは、File変数の名前にドットを付けなければならないことだけだ。

writeln("hello");        // 標準出力に書き込む
stdout.writeln("hello"); // 上記と同じ
file.writeln("hello");   // 指定したファイルに書き込む
D
eof()ファイルの終わりを判定する

eof()メンバー関数は、ファイルからの読み取り中にファイルの末尾に達したかどうかを判断する。ファイルの末尾に達した場合は、trueを返す。

例えば、次のループは、ファイルの末尾までアクティブのままになる。

while (!file.eof()) {
    // ...
}
D
std.fileモジュール

std.fileモジュールには、ディレクトリの内容を操作するときに役立つ関数や型が含まれている。例えば、existsを使用すると、ファイルシステム上にファイルまたはディレクトリが存在するかどうかを判断することができる。

import std.file;

// ...

    if (exists(fileName)) {
        // その名前のファイルまたはディレクトリがある

    } else {
        // その名前のファイルまたはディレクトリがない
    }
D
std.stdio.File struct

File構造体は、std.stdioモジュールに含まれている。これを使用するには、開くファイルの名前と、必要なアクセス権、つまりモードを指定する。この関数は、Cプログラミング言語のfopenで使用されるのと同じモード文字を使用する。

モード定義
r読み取りアクセス
ファイルは先頭から読み取り専用で開かれる
r+読み取りと書き込みアクセス
ファイルは先頭から読み取り可能で、書き込み可能
w書き込みアクセス
ファイルが存在しない場合、空の状態で作成される
ファイルが既に存在する場合、その内容がクリアされる
w+読み取りと書き込みアクセス
ファイルが存在しない場合、空の状態で作成される
ファイルが既に存在する場合、その内容がクリアされる
a追加アクセス
ファイルが存在しない場合、空のファイルとして作成される
ファイルが既に存在する場合、その内容は保持され、末尾に書き込むために開かれる
a+読み込みと追加アクセス
ファイルが存在しない場合、空のファイルとして作成される
ファイルが既に存在する場合、その内容は保持され、ファイルは先頭から読み込み、末尾に書き込むために開かれる

モード文字列に"b"文字を追加して、"rb"のように指定することもできる。これは、バイナリモードをサポートしているプラットフォームでは効果があるが、すべての POSIX システムでは無視される。

ファイルへの書き込み

ファイルは、まず書き込みモードの1つで開かれていなければならない:

import std.stdio;

void main() {
    File file = File("student_records", "w");

    file.writeln("Name  : ", "Zafer");
    file.writeln("Number: ", 123);
    file.writeln("Class : ", "1A");
}

文字列の章で覚えているように、 "student_records"は、不変文字で構成されるstring型である。そのため、変更可能なテキストを使用してファイル名を指定して(char[]など)、Fileオブジェクトを作成することはできない。必要に応じて、変更可能な文字列の.idupプロパティを呼び出して、不変のコピーを取得しよう。

上記のプログラムは、実行されたディレクトリ(プログラムの作業ディレクトリ)内にstudent_recordsという名前のファイルを作成または上書きする。

注釈:ファイル名には、そのファイルシステムで有効な任意の文字を使用できる。移植性を確保するため、ここでは一般的にサポートされているASCII文字のみを使用する。

ファイルからの読み込み

ファイルから読み込むには、まずファイルを読み取りモードで開く必要がある。

import std.stdio;
import std.string;

void main() {
    File file = File("student_records", "r");

    while (!file.eof()) {
        string line = strip(file.readln());
        writeln("read line -> |", line);
    }
}

上記のプログラムは、student_recordsという名前のファイルのすべての行を読み込み、その行を標準出力に表示する。

演習

ユーザーからファイル名を受け取り、そのファイルを開き、そのファイル内の空行以外の行をすべて別のファイルに書き込むプログラムを作成しよう。新しいファイルの名前は、元のファイルの名前に基づいて決定することができる。例えば、元のファイルがfoo.txtである場合、新しいファイルはfoo.txt.outになる。