先頭へ戻る

テキストファイルの読み書き 解答ページ | Programming Place Plus C言語編 第40章

Programming Place Plus トップページ -- C言語編 -- 第40章

先頭へ戻る

問題①

問題① 標準入力から受け取った内容を、テキストファイルへ書き出すプログラムを作成してください。細かい仕様はお任せします。


fgets関数で受け取り、fputs関数で出力させるだけです。fopen関数に指定するオープンモードは、"w" が良いでしょう。

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

int main(void)
{
    puts( "文字列を入力してください。" );
    char buf[80];
    fgets( buf, sizeof(buf), stdin );

    FILE* fp = fopen( "hello.txt", "w" );
    if( fp == NULL ){
        fputs( "ファイルオープンに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    if( fputs( buf, fp ) == EOF ){
        fputs( "ファイルへの書き込みに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    if( fclose( fp ) == EOF ){
        fputs( "ファイルクローズに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    return 0;
}

実行結果(標準出力)

文字列を入力してください。
hello!!

実行結果(hello.txt)

hello!!

問題②

問題② 標準入力から、テキストファイルのファイルパスを受け取り、そのファイルの内容を標準出力へ出力するプログラムを作成してください。


たとえば次のようになります。

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

int main(void)
{
    puts( "ファイルのパスを入力してください。" );
    char buf[80];
    fgets( buf, sizeof(buf), stdin );
    buf[strlen(buf)-1] = '\0';  // 末尾の改行文字を削除

    FILE* fp = fopen( buf, "r" );
    if( fp == NULL ){
        fputs( "ファイルオープンに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    while( 1 ){
        if( fgets( buf, sizeof(buf), fp ) == NULL ){
            if( feof( fp ) ){
                break;
            }
            else{
                fputs( "エラーが発生しました。\n", stderr );
                exit( EXIT_FAILURE );
            }
        }

        // 末尾の改行文字を取り除く
        char* p = strchr( buf, '\n' );
        if( p != NULL ){
            *p = '\0';
        }

        puts( buf );
    }

    if( fclose( fp ) == EOF ){
        fputs( "ファイルクローズに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    return 0;
}

入力ファイル(test.txt)

hello!!

実行結果(標準出力)

ファイルのパスを入力してください。
test.txt
hello!!

入力されるのはファイルパスなので、"mydoc/test.txt" のような表記も許されます。 結局のところファイルパスを解析する仕事は fopen関数に任せておけるので、標準入力から受け取った文字列を(末尾の改行文字だけ削除してから)素直に fopen関数に引き渡せば良いでしょう。

ところで、入力するファイルパスを "main.c" のように、このプログラム自身のソースファイルを指定してみるのも面白いかもしれません。自分自身のソースコードが出力されるのは、最初は不思議な感じがすると思います。

問題③

問題③ 問題②のプログラムを改造して、標準入力から指示された行の内容だけを、標準出力へ出力するようにしてください。


たとえば次のようになります。

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

int main(void)
{
    puts( "ファイルのパスを入力してください。" );
    char buf[80];
    fgets( buf, sizeof(buf), stdin );

    // 末尾の改行文字を削除して、コピー
    char path[80];
    buf[strlen(buf)-1] = '\0';
    strcpy( path, buf );


    int line;
    puts( "出力する行数を入力してください。" );
    fgets( buf, sizeof(buf), stdin );
    sscanf( buf, "%d", &line );
    if( line <= 0 ){
        fputs( "行数は 1 以上の整数でなければなりません。\n", stderr );
        exit( EXIT_FAILURE );
    }


    FILE* fp = fopen( path, "r" );
    if( fp == NULL ){
        fputs( "ファイルオープンに失敗しました。\n", stderr );
        return 0;
    }

    // 指定された行まで空読みを続ける
    for( int i = 0; i < line; ++i ){
        if( fgets( buf, sizeof(buf), fp ) == NULL ){
            if( feof( fp ) ){
                // 指定された行数に達する前に、ファイルの終端に来てしまった
                printf( "%d行目は存在しません。\n", line );
                break;
            }
            else{
                fputs( "エラーが発生しました。\n", stderr );
                exit( EXIT_FAILURE );
            }
        }

        // 末尾の改行文字を取り除く
        char* p = strchr( buf, '\n' );
        if( p != NULL ){
            *p = '\0';
        }
    }

    // 指定行が発見できたら、その行の内容を出力
    if( i == line ){
        puts( buf );
    }

    if( fclose( fp ) == EOF ){
        fputs( "ファイルクローズに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    return 0;
}

入力ファイル(test.txt)

1行目
2行目
3行目
4行目
5行目

実行結果(標準出力)

ファイルのパスを入力してください。
test.txt
出力する行数を入力してください。
4
4行目

存在しない行の処置が問題になります。

まず、「-2行目」のような確実にありえない指定については、ファイルのオープンよりも前にさっさとエラーにしてしまうのが簡単でしょう。 ファイルをオープンしてから対処すると、クローズ処理を通過させる必要があったり、ファイルオープンに失敗した場合には複数のエラーが重複することになりと、 面倒になってきます。

また、指定された行数が、指定されたファイルの総行数よりも大きい可能性があります。目的の行を読み込むよりも先に、fgets関数がヌルポインタを返し、feof関数が 0以外になったときがこれに該当します。この場合にも、分かりやすいエラーメッセージを出力しておくと良いでしょう。

なお、指定された行を読み込むまでは、fgets関数を繰り返し呼び続け、読み込んだ文字列は放っておけば良いです。

問題④

問題④ 1文字ずつ読み書きする標準関数を使って、既存のテキストファイルのコピーを作り出すプログラムを作成してください。


たとえば次のようになります。

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

int main(void)
{
    // コピー元ファイル
    FILE* fpSrc = fopen( "src.txt", "r" );
    if( fpSrc == NULL ){
        fputs( "ファイルオープンに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    // コピー先ファイル
    FILE* fpDest = fopen( "dest.txt", "w" );
    if( fpDest == NULL ){
        fputs( "ファイルオープンに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    // コピー作業
    while( 1 ){
        int c = fgetc( fpSrc );
        if( c == EOF ){
            if( feof( fpSrc ) ){
                break;
            }
            else if( ferror( fpSrc ) ){
                fputs( "エラーが発生しました。\n", stderr );
                exit( EXIT_FAILURE );
            }
            else{
                // 有効な文字なので、そのまま続行
            }
        }

        if( fputc( c, fpDest ) == EOF ){
            fputs( "エラーが発生しました。\n", stderr );
            exit( EXIT_FAILURE );
        }
    }


    if( fclose( fpSrc ) == EOF ){
        fputs( "ファイルクローズに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }
    if( fclose( fpDest ) == EOF ){
        fputs( "ファイルクローズに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    return 0;
}

入力ファイル(src.txt)

Hello, World

実行結果(標準出力)

実行結果(dest.txt)

Hello, World

コピー元となるファイルを "r"モードで開き、コピー先となるファイルを "w"モードで開いておきます。 そして、fgetc関数で 1文字読み込んでは、ファイルの終わりに達していないか(あるいはエラーが起きていないか)を確かめ、まだ続行できるようなら、fputc関数でコピー先のファイルへ書き込みます。

問題⑤

問題⑤ テキストファイルのデータ中に含まれているタブ文字を、半角空白に置き換えた結果を、別のファイルへ出力するプログラムを作成してください。 ただし、タブの幅は自由に決めてください。


たとえば次のようになります。

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

#define TAB_SIZE  (4)   // タブの幅

int main(void)
{
    // コピー元ファイル
    FILE* fpSrc = fopen( "src.txt", "r" );
    if( fpSrc == NULL ){
        fputs( "ファイルオープンに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    // コピー先ファイル
    FILE* fpDest = fopen( "dest.txt", "w" );
    if( fpDest == NULL ){
        fputs( "ファイルオープンに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    int putCount = 0;  // 現在の行に書き込んだ文字数のカウント

    while( 1 ){
        int c = fgetc( fpSrc );
        if( c == EOF ){
            if( feof( fpSrc ) ){
                break;
            }
            else if( ferror( fpSrc ) ){
                fputs( "エラーが発生しました。\n", stderr );
                exit( EXIT_FAILURE );
            }
            else{
                // 有効な文字なので、そのまま続行
            }
        }

        if( c == '\t' ){
            // タブ文字なら、半角空白に置き換える

            int spaceSize = TAB_SIZE - putCount % TAB_SIZE;

            for( int i = 0; i < spaceSize; ++i ){
                if( fputc( ' ', fpDest ) == EOF ){
                    fputs( "エラーが発生しました。\n", stderr );
                    exit( EXIT_FAILURE );
                }
                putCount++;
            }
        }
        else{
            // タブ文字以外は、そのまま書き込む
            if( fputc( c, fpDest ) == EOF ){
                fputs( "エラーが発生しました。\n", stderr );
                exit( EXIT_FAILURE );
            }

            if( c == '\n' ){
                // 改行文字のときは、書き込んだ文字数をクリアする
                putCount = 0;
            }
            else{
                putCount++;
            }
        }
    }


    if( fclose( fpSrc ) == EOF ){
        fputs( "ファイルクローズに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }
    if( fclose( fpDest ) == EOF ){
        fputs( "ファイルクローズに失敗しました。\n", stderr );
        exit( EXIT_FAILURE );
    }

    return 0;
}

入力ファイル(src.txt)
※____ の部分がタブ文字であるとする。タブ幅は 4

ここにタブ__がある
タブ____はここにある
ここにもタブ____がある

実行結果(標準出力)

実行結果(dest.txt)

ここにタブ  がある
タブ    はここにある
ここにもタブ    がある

意外と、タブ文字がどういうものか分かっていない人がいるようですから、分からなかった人はちゃんと調べてみてください。

たとえば、タブ幅が 4 というのは、半角空白 4文字に置き換わるという意味ではありません。 要は、4 の倍数の文字数のところで位置をそろえるということなので、現在の行にどれだけの文字数を書き込んだかを記憶しておく必要があります。 そして、タブ文字が現れたら、タブ幅の倍数になるまで、空白文字を充填すれば良いということになります。



参考リンク


------------------------------------------------------------------------

更新履歴

'2018/4/20 「NULL」よりも「ヌルポインタ」が適切な箇所について、「ヌルポインタ」に修正。

≪さらに古い更新履歴を展開する≫



第40章のメインページへ

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー