HOWTO · C
C 言語でのファイルリダイレクトの使用
この記事では、C 言語でファイルリダイレクトを使用する方法を説明します。
このページの内容
コマンドラインからCプログラムの入力や出力をリダイレクトするには、シェルの演算子 <、>、>> を使います。シェルはプログラムの標準ストリームをファイルに接続します。Cコードでは、fopen で名前を指定したファイルを開いたり、freopen で標準ストリームを切り替えたりできます。いずれもデータを扱う方法ですが、動作は異なります。
Cにおけるファイルリダイレクトの仕組み
Cのプロセスは、入力用の stdin、通常の出力用の stdout、診断メッセージ用の stderr という3つの標準ストリームから始まります。通常、シェルはこれらを端末に接続します。プログラムの起動時にリダイレクトを指定すると、シェルがその接続先を変更します。そのため、プログラムはストリームが端末とファイルのどちらにつながっているかを意識せずに、fgets、printf、fprintf(stderr, ...) を呼び出せます。
一方、fopen は、コードから直接読み書きする別個の FILE * を返します。プログラム側で複数のファイルから選ぶ必要がある場合、複数のファイルを同時に扱う場合、またはファイルの有効期間を制御する場合に使います。入力元や出力先を呼び出し側に選ばせる実行では、シェルのリダイレクトを使います。
> と » によるプログラム出力のリダイレクト
次の短いプログラムは、stdout に1行を書き込みます。
この例を実行します。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <stdio.h>
int main(void) {
puts("Report complete");
return 0;
}
プログラムの1行は stdout に出力されます。この例には実行時の出力記録は付属していません。プログラムを report という名前でコンパイルしたら、./report > report.txt を実行すると report.txt が作成されるか、既存の内容が置き換えられます。末尾に追記するには ./report >> report.txt を使います。> はプログラムの起動前に出力先ファイルの既存内容を切り詰め、>> はファイルの末尾に書き込みます。シェルの構文や利用できるコンパイラコマンドは、プラットフォームによって異なります。
リダイレクト先を開くのはシェルです。開く処理に失敗すると、通常はシェルが問題を報告し、プログラムを起動しません。プログラム自身が出力先を開いていないため、その失敗を fopen の失敗として検出することはできません。診断メッセージを別のファイルに保存するには、./report > report.txt 2> errors.txt を使います。2> は stderr に使われる慣例的なファイル記述子をリダイレクトします。コード例の文字列や表示ラベルは、例のバイト列を変更しないため英語のままです。
< によるプログラム入力のリダイレクト
入力元を呼び出し側に選ばせる場合は、stdin から読み込みます。次のプログラムは、リダイレクトされたファイルの終端に達するまで行を読み取ります。
このセクションのコードと表示されるラベルや出力は英語のままです。
#include <stdio.h>
int main(void) {
char line[128];
while (fgets(line, sizeof line, stdin) != NULL) {
if (fputs(line, stdout) == EOF) {
perror("writing stdout");
return 1;
}
}
if (ferror(stdin)) {
perror("reading stdin");
return 1;
}
return 0;
}
copy-input という名前でコンパイルし、./copy-input < input.txt を実行します。fgets はバッファ容量より1文字少ない数まで読み取り、終端のヌル文字を追加します。改行を読み取った場合、その改行もバッファに残ります。そのため、長い1行が複数回に分けて返されることがあります。ループの後で ferror を確認し、読み取りエラーと通常のファイル終端を区別してください。この例の動作は示されたCのストリーム操作に基づくもので、実測された出力は提示されていません。コード例の文字列や表示ラベルは、例のバイト列を変更しないため英語のままです。
Cプログラム内で標準ストリームをリダイレクトする
プログラム自身が出力先を選ぶ必要がある場合、freopen で標準ストリームを開き直せます。引数にはパス、"w" や "a" などのモード、開き直すストリームを指定します。たとえば freopen("run.log", "a", stdout) は、それ以降の標準出力を run.log の末尾に追記するようにします。書き込みの前に戻り値を確認してください。開き直しに失敗すると NULL が返ります。これはプロセスのストリームを変更します。一方、fopen は別のストリームを返し、stdout はそのままです。
リダイレクトをプログラム自身の動作に含める場合は、この方法を使います。出力先を呼び出し側に選ばせる場合は、stdout を変更せずに > または >> を使います。freopen は標準Cの機能ですが、失敗後にストリームを再び開く際の詳細は実装によって異なることがあります。戻り値が NULL の場合はそれを処理し、リダイレクトに成功したものとして処理を続けないでください。
エラーを処理し、適切な方法を選ぶ
失敗する可能性のある操作はすべて確認してください。返されたストリームを使う前に fopen と freopen の結果を調べ、fprintf や fputs などの出力関数が失敗していないか確認します。バッファに残った出力がファイルに書き込まれたことが重要な場合は、fclose の結果も確認してください。入力ループでは、fgets が NULL を返す理由として、ファイル終端と読み取りエラーの両方が考えられます。ferror を調べて区別してください。perror は、現在の errno に関連する失敗について、オペレーティングシステムのメッセージを表示できます。
ファイルの報告するバイト長から行バッファの大きさを決めないでください。入力元がパイプや端末の場合もあり、行の長さが用意したバッファを超えることもあります。チャンク単位で処理できるなら、固定サイズのバッファと繰り返しの fgets 呼び出しが適しています。長さに上限のない論理行を扱うには、チャンクを受け取るたびにバッファを拡張し、メモリ確保と算術演算の上限を確認します。または、移植性の制約を明記したうえで、プラットフォーム固有の行読み込み機能を使います。
実行時の標準ストリームを接続するにはシェルのリダイレクトを使い、Cで追加のファイルを管理するには fopen を使い、プログラム自身が標準ストリームを置き換えるには freopen を使います。通常の出力をリダイレクトする場合は診断メッセージを stderr に出し、操作を行った層で失敗を確認してください。