C言語編 第44章 ファイルシステム

この章の概要

この章の概要です。

ファイルの削除

標準ライブラリ関数には、ファイルを削除する remove関数(⇒リファレンス)が用意されています。

int remove(const char* filename);

引数にファイル名を指定すると、そのファイルを削除します。 戻り値は、成功すれば 0 が、失敗すると 0以外が返されます。

厳密には、remove関数は、指定されたファイルをアクセスできない状態にするとされていますが、 これはほとんどの環境では、ファイルを削除することと同じ意味です。

通常、指定したファイルがどこかからアクセスされている場合、ファイルの削除はできないかも知れません。 ですから当然ながら、ファイルが 100%削除できるという期待を持つことはできません。

ファイル名を変更する

rename関数(⇒リファレンス)を使うと、ファイルの名前を変えることができます。

int rename(const char* old, const char* new);

第1引数に対象ファイルの名前(これが変更前の名前)を指定し、第2引数に新しい名前を指定します。 戻り値は、成功すれば 0 が、失敗すると 0以外が返されます。

他の関数と同様、どんな状況下をエラーとみなすかは環境によるところが大きいですが、 いずれにせよ、100% 成功するという期待を持ってはいけません。

ファイルの移動

ファイルを、別のディレクトリへ移動させるような操作は、実は rename関数で行えます。 第2引数に、移動先のディレクトリ名を含めてパス指定すれば良いです。

if( rename( "test.txt", "other_dir/test.txt" ) != 0 ){
	/* 失敗 */
}

ただし、これが動作するかどうかは、やはり環境に依存します。 失敗の原因次第では、ファイルのコピーのような方法で、移動先にファイルの複製を作り、 移動元のファイルを remove関数(⇒リファレンス)で削除するという手を使って回避せざるを得ません。

例えば、移動元と移動先とで、使用しているファイルシステムが異なる場合、 そのための変換が必要となるため、失敗することがあるようです。

ファイルのコピー

ファイルのコピーを簡単に行える標準関数は存在しません。 そのため、2つのファイルをオープンし、中身を読み書きして、複写していくしかありません。

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	FILE* fpSrc;
	FILE* fpDest;
	char c;
	int error = 0;


	fpSrc = fopen( "test.txt", "rb" );
	fpDest = fopen( "test_copy.txt", "wb" );
	if( fpSrc == NULL || fpDest == NULL ){
		error = 1;
	}

	if( !error ){
		while( 1 ){
			/* バイナリデータとして 1Byteずつ読み込み、1Byteずつ書き込む */

			fread( &c, sizeof(c), 1, fpSrc );
			if( feof( fpSrc ) ){
				break;
			}
			if( ferror( fpSrc ) ){
				error = 1;
				break;
			}

			fwrite( &c, sizeof(c), 1, fpDest );
			if( ferror( fpDest ) ){
				error = 1;
				break;
			}
		}
	}

	if( fpDest != NULL ){
		if( fclose( fpDest ) == EOF ){
			error = 1;
		}
	}
	if( fpSrc != NULL ){
		if( fclose( fpSrc ) == EOF ){
			error = 1;
		}
	}
	
	if( error ){
		fputs( "エラーが発生しました。\n", stderr );
	}

	return 0;
}

入力ファイル (test.txt)

test.txt

テストファイル

出力ファイル (test_copy.txt)

test.txt

テストファイル

実行結果




どんな種類のファイルであってもコピーできるようにするためには、バイナリモードで扱うことが必要です。 そうでないと、改行文字が特別な扱いを受けて、勝手に変換されてしまう可能性があります(第42章参照

ファイルのコピーを実装する方法については、 「逆引き ファイルをコピーする」でも取り上げています。

ファイルの存在を調べる

ある名前を持ったファイルが、実際に存在しているかどうかを調べたいことがあります。 ファイルの存在を確認するための標準関数はありませんが、fopen関数(⇒リファレンス)で代用するという手法がよく使われます。

#include <stdio.h>

int main(void)
{
	FILE* fp;

	fp = fopen( "test.txt", "r" );
	if( fp == NULL ){
		puts( "存在しない。" );
	}
	else{
		puts( "存在する。" );
		fclose( fp );  /* 忘れずにクローズすること */
	}

	return 0;
}

実行結果

存在する。

fopen関数は、"r"モードでオープンしようとした場合、第1引数で指定したファイルのオープンに失敗したら、NULL を返します。 これを利用している訳です。 NULL 以外が返された場合には、正常にオープンできたということなので、ファイルは間違いなく存在しています。
なお、存在の確認だけが目的ならば、オープンしてしまったファイルを忘れずにクローズしておきましょう。 (存在しなかったときには NULL が返されているので、存在したときに通過するコードの中でクローズします)。

しかしこの方法では、ファイルが存在しないという理由以外で NULL を返す可能性があるため、厳密には正確とは言えません。 例えば、指定したファイルの読み取りアクセスが許可されていないような状況下が考えられます。
これが問題であれば、この方法は使えませんが、その場合、自分の環境に特化した汎用性の低い方法を探すしかありません。 方法の幾つかを、「逆引き ファイルの存在を確認する」で取り上げているので、 そちらを参照して下さい。

ファイルのサイズを調べる

ファイルのサイズは、ファイルをオープンしても良ければ、標準関数だけでも調べられます。 ファイルをオープンせずにサイズを調べることは、標準関数だけでは不可能です。

ファイルをオープンしてから調べる方法は、バイナリモードでファイルをオープンし、ファイルの末尾へシークさせた後、 その位置のファイルポジションを調べるというものです。 この方法は、第42章で例に挙げています。

system関数の利用

C言語の標準関数だけでは不可能でも、実行環境の助けを借りれば、できることは増えます。
標準関数である system関数(⇒リファレンス)を使うと、 コマンドプロセッサへコマンド文字列を渡し、そこで処理させることができます。 コマンドプロセッサとは、コンソールアプリケーションを実行するプログラムのことです。 コマンドプロセッサには、幾つかコマンドが用意されており、そのコマンドを使って様々な処理が行えますが、 コマンドの種類や使い方は環境によって異なります。

system関数は、次のように宣言されています。

int system(const char* s);

引数に、コマンドプロセッサに渡す文字列を指定します。 戻り値の意味は、環境によって異なります。

Windows の場合

Windows環境では、system関数を使って、コマンドプロンプトへ文字列を渡して実行してもらうことができます。
コマンドプロンプトでは、DOSコマンドと呼ばれる一連のコマンドが使用でき、 このコマンドの中には、ファイル操作に関わるものも数多く存在します。 system関数を通して、DOSコマンドを使うことによって、ファイルのコピーを実現する例は、次のようになります。

#include <stdlib.h>

int main(void)
{
	system( "copy /B test.txt test_copy.txt" );

	return 0;
}

入力ファイル (test.txt)

test.txt

テストファイル

出力ファイル (test_copy.txt)

test.txt

テストファイル

実行結果

        1 個のファイルをコピーしました。

"copy /B test.txt test_copy.txt" というのが、コマンドプロンプトに渡されて実行されます。

これは copyコマンドを実行させており、test.txt を test_copy.txt へコピーします。 /B はオプション指定で、バイナリファイルとして扱うことを意味しています。

OS X の場合

OS X では、system関数を使って、ターミナルへ文字列を渡して実行してもらうことができます。 OS X では、UNIXコマンドを使うことができますから、system関数で UNIXコマンドを渡して実行してもらえば、 ファイルコピーを実現することもできます。

#include <stdlib.h>

int main(void)
{
	system( "cp test.txt test_copy.txt" );

	return 0;
}

入力ファイル (test.txt)

test.txt

テストファイル

出力ファイル (test_copy.txt)

test.txt

テストファイル

実行結果




"cp test.txt test_copy.txt" というのが、ターミナルに渡されて実行されます。 cp がコマンドの名前で、copy の略です。 これで test.txt を test_copy.txt にコピーできます。

ファイルコピーのところで説明したように、C言語の標準関数だけを使ってファイルコピーを実現するには、 1Byteずつ地道にコピーしていくような処理を書くしかありません。
system関数を使った今回の例だと、非常に楽ができます。 その一方で、この方法は環境に依存した、移植性の無い方法ということになります( system関数自体は使えますが、渡す文字列は環境に応じて変えないといけません)。


練習問題

問題@ remove関数(⇒リファレンス)を試してみて下さい。 また、この関数は自分の使っている環境では、どんなときに失敗するでしょう?

問題A C言語の標準関数には、ディレクトリを操作するものは存在しません。 自分の使っている環境にある非標準の機能としては存在するでしょうか? 例えば、ディレクトリを作成する方法を調べてみて下さい。


解答ページはこちら

参考リンク

更新履歴

'2017/6/17 「ファイルのコピー」の項のサンプルプログラムを修正。
逆引きへのリンクを追加。
「WindowsAPI の利用」の項を削除(逆引きの方で対応している)。

'2015/8/29 flose関数の戻り値もチェックするようにした。

'2014/1/18 OS X へ対応。

'2010/6/18 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ