カンパ〜イ!←最初の1杯目、なに頼む?

いつもお世話になります。

今、ファイル操作を勉強しています。
sample.txtに下記のデータが格納されています。

【sample.txt】
---------------------
yamada.txt | 29
suzuki.txt | 25
kitamura.txt | 30
endo.txt | 32
---------------------

char *name="suzuki.txt";

sample.txtのデータがchar nameと一致する、
ファイルの行を削除し、行を詰めたいのですが
どうしたらいいのでしょうか?
結果として、sample.txtが、

---------------------
yamada.txt | 29
kitamura.txt | 30
endo.txt | 32
---------------------

のように、一致したsuzuki.txtの行が削除され、
行が詰められている状態にしたいです。

どうぞよろしくお願い致します。

A 回答 (3件)

★アドバイス


・単純に『sample.txt』を読み込み、単純に一時ファイル『sample.tmp』に書き出す。
 このときに『char name』と一致する行だけを書き出さないようにすれば良い。
 その後に『sample.txt』を削除して『sample.tmp』のファイル名を『sample.txt』に
 変更する方法もあります。ただし C 標準関数ではファイル名の変更する関数がなく
 処理系によっては _rename() という関数があったりします。Windows 環境なら API
 関数を使ってリネームできます。→MoveFile()
・今回は sample.txt のデータをメモリに一括で読み込み処理した方がいいのかも。
 読み込む方法も回答者 No.1 さんのようにすべてのデータを1つのメモリ領域に
 読み込む方法があります。こちらは No.1 さんのを参照。
 私は行単位でメモリに格納して書き出す方を紹介します。
 (1)ファイルをテキストでオープン(fopen)
 (2)適当なサイズのメモリを確保(malloc)
 (3)行単位で読み込む(fgets)
 (4)『char name』と一致する行以外をメモリに格納(strstr)
 (5)(4)でメモリが不足しているならメモリを拡張(realloc)
 (6)ファイルが終わるまで(2)~(5)を繰り返す
 (7)ファイルをクローズ(fclose)
 こんな感じでどうですか?
 今回はメモリの確保(malloc)、拡張(realloc)、解放(free)を使ってみます。
・下に実装例を載せます。これを参考にもっと良い方法を探して下さい。

サンプル:
size_t leng;
size_t size = (10 * 1024); // 最初は 10 KBのメモリ量
size_t free = size;
char *buff = (char*)malloc(size);
char *tail = buff;
char *stop = buff + size;
char *name = "suzuki.txt"; // 削除するファイル名
char *mp;
FILE *fp;

// 確保チェック
if ( buff == NULL ){
 printf( "メモリが確保できません。\n" );
 exit( 255 );
}
// 読み込み
if ( (fp = fopen("sample.txt","r")) != NULL ){
 while ( fgets(tail,(int)free,fp) != NULL ){
  if ( strstr(tail,name) != NULL ){
   continue; // 削除ファイルなら次の読み込みへ
  }
  leng = strlen( tail );
  tail += leng;
  free -= leng;
  
  if ( free < 1024 ){  // 空き領域が 1 KB 以下なら
   leng = (10 * 1024); // メモリ量を 10 KB 増加(拡張)
   size += leng;
   free += leng;
   
   if ( (mp = (char*)realloc(buff,size)) == NULL ){
    printf( "メモリが足りませんでした。\n" );
    free( buff ); // 安全なため
    fclose( fp ); // 安全なため
    exit( 255 );
   }
   // 再設定
   leng = strlen( mp );
   buff = mp;
   tail = mp + leng;
   stop = mp + size;
  }
 }
 fclose( fp );
}
// 書き出し
if ( (fp = fopen("sample.txt","w")) != NULL ){
 fputs( buff, fp );
 fclose( fp );
}

最後に:
・ソースがちょっとがないですが読み取ってみて下さい。
 また上記のサンプルを関数にして、ファイルから動的にメモリに読み込めるように
 改良すれば今後いろいろと利用できます。今回は strstr() 部分で name に一致した
 行はメモリには追加しない処理をしています。ここの処理をなくせば改良できます。
・あと書き出しの方はメモリに読み込んだ内容を単純にファイルに出力すれば良いだけ
 なので fopen、fputs、fclose の簡単な処理になっています。
 name のチェックと削除はメモリに読み込み時に処理しているからです。
・以上。realloc 関数については下の『参考URL』をどうぞ。

参考URL:http://www9.plala.or.jp/sgwr-t/lib/realloc.html
    • good
    • 1

あっと、ミステイクです。


>メモリにすべて読み込んで概要行を
メモリにすべて読み込んで該当行を

それと(3)のstrncmpの方法ではメモリ保護エラーが出る可能性があるので、これも訂正します。なぜか分かりますか?

新しい(3)です。
(3)char nameの名前と一致する文字列がバッファにあるかを、1文字づつ比較して確認します。char name側が文字列終端(\0)に達したら一致したと判定します。途中で文字が違ったり、バッファ側が改行コード(0x0d,0x0a)だったり、バッファサイズ外に出そうになったら不一致として判定します。
    • good
    • 0

ファイルの大きさが数十MB以下であれば、メモリにすべて読み込んで概要行を削除して書き出すのが一般的だと思います。



練習問題的な処理ではなく、実用レベルでプログラムを書くには次のような流れで処理します。
(1)ファイルサイズを調べて、読み込むバッファをmallocで確保します。読み込むサイズをバッファサイズとして記録します。
(2)ファイルをバイナリでオープンして、freadで一括バッファに読み込みます。処理ポインタにバッファの先頭アドレスを入れます。
(3)char nameの名前と一致する文字列がバッファにあるかを、strncmpでチェックします。strncmpを使う理由は、バッファ側の文字列終端(\0)が無いためです。
(4)一致したら、その行の終端である改行コード(0x0d,0x0a)をサーチします。これで、削除する行の先頭アドレスと終了アドレスが得られます。
(5)(4)の続きで、memcpy(先頭アドレス,終了アドレス+1,転送サイズ);で内容コピーをして間を詰めます。転送サイズは、バッファの終端アドレス-終了アドレスで求めます。転送後は、間を詰めたのでバッファサイズを修正します(方法は考えてみてください)。
(6)一致しなかったら、改行コード(0x0d,0x0a)をサーチして処理ポインタを次の行に移します。
(7)(3)~(6)までを処理ポインタがバッファの最後に達するまで繰り返します。
(8)読み込んだときと同じファイルをバイナリで出力オープンして、バッファの内容をバッファサイズ分ファイルに書き出します。

(注意)(4)と(6)で改行コードをサーチする場合に、バッファ終端が来るかも知れませんので、それを考慮する必要があります。

もっと高速な方法はありますが、とりあえず例として。
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!

このQ&Aを見た人はこんなQ&Aも見ています


おすすめ情報

このQ&Aを見た人がよく見るQ&A