C语言中逐行读取文本文件

72

我一直在为我的CIS课程做一个小练习,但对于C语言读取文件的方法感到非常困惑。实际上,我只需要逐行读取文件,并使用每行所收集到的信息来进行一些操作。我尝试使用getline方法和其他方法,但都没有成功。 我的代码目前如下:

int main(char *argc, char* argv[]){
      const char *filename = argv[0];
      FILE *file = fopen(filename, "r");
      char *line = NULL;

      while(!feof(file)){
        sscanf(line, filename, "%s");
        printf("%s\n", line);
      }
    return 1;
}

我现在用sscanf方法遇到了段错误,但不确定原因。作为一个完全的C新手,我想知道是否有一些大局方面的东西被我忽略了。谢谢。


2
这段代码甚至不应该编译通过。sscanf(line, filename, "%s"); 应该改为 sscanf(line, file, "%s"); - Mawg says reinstate Monica
1
注意while (!feof(file))是错误的 - Jonathan Leffler
1
可能是C逐行读取文件的重复问题。 - Ciro Santilli OurBigBook.com
4个回答

156

这么少的代码行就有这么多问题。我可能忘记了一些:

  • argv[0] 是程序名称,而不是第一个参数;
  • 如果你想读取一个变量,你必须分配它的内存;
  • 永远不要在 feof 上循环,而是在 IO 函数上循环直到它失败,feof 然后用于确定失败的原因;
  • sscanf 用于解析一行文本,如果你想解析一个文件,请使用 fscanf;
  • "%s" 作为 ?scanf 家族的格式将在第一个空格处停止;
  • 读取一行的标准函数是 fgets;
  • 从 main 中返回 1 表示失败。

所以

#include <stdio.h>

int main(int argc, char* argv[])
{
    char const* const fileName = argv[1]; /* should check that argc > 1 */
    FILE* file = fopen(fileName, "r"); /* should check the result */
    char line[256];

    while (fgets(line, sizeof(line), file)) {
        /* note that fgets don't strip the terminating \n, checking its
           presence would allow to handle lines longer that sizeof(line) */
        printf("%s", line); 
    }
    /* may check feof here to make a difference between eof and io failure -- network
       timeout for instance */

    fclose(file);

    return 0;
}

29
在返回之前,不要忘记使用 fclose(file) 关闭文件。 - vivisidea
9
fclose(file) 实际上是不必要的,因为它发生在 main 函数中并且自动关闭所有打开的文件缓冲区。 - Leandros
20
@Leandros,保险起见总是比抱歉好! - vallentin
2
即使在 main 函数结束时,对于初学者来说仍然很有必要。在 C 语言中,FILE* 对象是带缓冲区的,因此如果写入文件时没有调用 fclose 函数,则可能会导致部分数据未被刷新。 - rovaughn
2
嗨,@alecRN:你确定吗?据我所知,在流上的缓冲输出在程序通过调用exit终止时会自动刷新(参见:https://www.gnu.org/software/libc/manual/html_node/Flushing-Buffers.html),而操作系统将决定何时刷新(可以调用fsync)。执行结束时有一个隐式的exit_group调用,您可以使用strace和nm查看它。我想这不是由gcc添加的,因为没有这样的符号,可能是由运行时添加的。即使_exit也会关闭打开的文件描述符。无论如何,我同意您关闭打开的文件的做法是好习惯 / Ángel - Angel
显示剩余2条评论

9
要从文件中读取一行,你应该使用fgets函数:它会从指定的文件中读取一个字符串,直到换行符或EOF
在你的代码中使用sscanf根本行不通,因为你把filename作为格式字符串来读取line中的常量字符串文本%s
SEGV错误的原因是你向line指向的未分配内存中写入数据。

6

除了其他答案之外,在最近的C库(符合Posix 2008标准)中,您可以使用getline。请参见this answer(有关问题的答案)。


5

假设你正在处理其他分隔符,例如 \t 制表符,而不是 \n 换行符。

更通用的分隔符方法是使用 getc() 函数,它每次获取一个字符。

请注意,getc() 返回一个 int 类型的值,因此我们可以使用 EOF 进行相等性测试。

其次,我们定义了一个类型为 char 的数组 line[BUFFER_MAX_LENGTH],以便在堆栈上存储最多 BUFFER_MAX_LENGTH-1 个字符(我们必须将最后一个字符保存为 \0 终止符)。

使用数组避免了使用 mallocfree 在堆上创建正确长度的字符指针的需要。

#define BUFFER_MAX_LENGTH 1024

int main(int argc, char* argv[])
{
    FILE *file = NULL;
    char line[BUFFER_MAX_LENGTH];
    int tempChar;
    unsigned int tempCharIdx = 0U;

    if (argc == 2)
         file = fopen(argv[1], "r");
    else {
         fprintf(stderr, "error: wrong number of arguments\n"
                         "usage: %s textfile\n", argv[0]);
         return EXIT_FAILURE;
    }

    if (!file) {
         fprintf(stderr, "error: could not open textfile: %s\n", argv[1]);
         return EXIT_FAILURE;
    }

    /* get a character from the file pointer */
    while(tempChar = fgetc(file))
    {
        /* avoid buffer overflow error */
        if (tempCharIdx == BUFFER_MAX_LENGTH) {
            fprintf(stderr, "error: line is too long. increase BUFFER_MAX_LENGTH.\n");
            return EXIT_FAILURE;
        }

        /* test character value */
        if (tempChar == EOF) {
            line[tempCharIdx] = '\0';
            fprintf(stdout, "%s\n", line);
            break;
        }
        else if (tempChar == '\n') {
            line[tempCharIdx] = '\0';
            tempCharIdx = 0U;
            fprintf(stdout, "%s\n", line);
            continue;
        }
        else
            line[tempCharIdx++] = (char)tempChar;
    }

    return EXIT_SUCCESS;
}

如果必须使用char *,那么你仍然可以使用这段代码,但是一旦line[]数组填满了一行的输入,就需要对其进行strdup()操作。在完成后,必须free这个重复的字符串,否则会出现内存泄漏:

#define BUFFER_MAX_LENGTH 1024

int main(int argc, char* argv[])
{
    FILE *file = NULL;
    char line[BUFFER_MAX_LENGTH];
    int tempChar;
    unsigned int tempCharIdx = 0U;
    char *dynamicLine = NULL;

    if (argc == 2)
         file = fopen(argv[1], "r");
    else {
         fprintf(stderr, "error: wrong number of arguments\n"
                         "usage: %s textfile\n", argv[0]);
         return EXIT_FAILURE;
    }

    if (!file) {
         fprintf(stderr, "error: could not open textfile: %s\n", argv[1]);
         return EXIT_FAILURE;
    }

    while(tempChar = fgetc(file))
    {
        /* avoid buffer overflow error */
        if (tempCharIdx == BUFFER_MAX_LENGTH) {
            fprintf(stderr, "error: line is too long. increase BUFFER_MAX_LENGTH.\n");
            return EXIT_FAILURE;
        }

        /* test character value */
        if (tempChar == EOF) {
            line[tempCharIdx] = '\0';
            dynamicLine = strdup(line);
            fprintf(stdout, "%s\n", dynamicLine);
            free(dynamicLine);
            dynamicLine = NULL;
            break;
        }
        else if (tempChar == '\n') {
            line[tempCharIdx] = '\0';
            tempCharIdx = 0U;
            dynamicLine = strdup(line);
            fprintf(stdout, "%s\n", dynamicLine);
            free(dynamicLine);
            dynamicLine = NULL;
            continue;
        }
        else
            line[tempCharIdx++] = (char)tempChar;
    }

    return EXIT_SUCCESS;
}

1
我会对任何出现while(!feof(file))的代码进行负面评价,即使在极少数情况下它不会造成损害(请注意,在这种情况下它可能永远不会为真,因为有一个break语句来退出循环,使用while(true)也可以)。太多人认为这是正确的习惯用法。 - AProgrammer
我完全不知道那是个问题。说实话,我想更多地了解一下这个问题。那种用法有什么问题呢? - Alex Reynolds
有很多问题涉及到这个,例如https://dev59.com/jG035IYBdhLWcg3wbPU5。 - AProgrammer
2
好的,我修复了循环。谢谢你的指点。我每天都会学到新东西。 - Alex Reynolds

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接