C语言 如何验证fgets()是否读取一行并处理错误

t1qtbnec  于 2023-03-28  发布在  其他
关注(0)|答案(3)|浏览(183)

我正在使用fgets逐行读取文件

char buffer[4096];

    while (fgets(buffer, sizeof(buffer), file__1) != NULL) {
           
           fprintf(file__2, "%s", buffer);
    }

但是,man page of fgets()在“示例”部分中说明了这一点

while (fgets(line, line_max + 1, fp) != NULL) {
               // Verify that a full line has been read ...
               // If not, report an error or prepare to treat the
               // next time through the loop as a read of a
               // continuation of the current line.
               ...
               // Process line ...
               ...
           }

我的问题是,当fgets失败时,我如何“验证已读取整行”?

guicsvcw

guicsvcw1#

我如何才能“验证已读取整行”?
如果fgets返回NULL,则意味着

  • 在阅读单个字符(甚至不是换行符)之前遇到文件结束,或者
  • 在流上发生错误。

因此,当fgets返回NULL时,您应该始终假设尚未读取整行。
然而,当fgets不返回NULL时,则意味着对fgets的函数调用成功。但这并不一定意味着已经读取了一整行。有可能fgets成功地填充了缓冲区,但该行太长,无法放入缓冲区。因此,确定是否读取了整行的最简单方法是检查fgets返回的字符串是否包含换行符,例如使用函数strchr
即使没有找到换行符,也不一定意味着没有读取整行。虽然POSIX defines a line to end with a newline character,但有可能您正在阅读的文本文件不遵循此规则。有可能您正在读取的文本文件的最后一行没有换行符,因此您会遇到文件结束之前没有换行符的情况。在这种情况下,即使在到达文件结尾之前没有遇到换行符,也可以将该行视为“一整行”。
在处理用户输入时,也可能会遇到文件结束而没有换行符的情况。例如,在Linux上,用户可以按CTRL+D来使用键盘输入文件结束。(在Microsoft Windows上,您可以使用CTRL+Z来执行相同的操作,但与Linux相比,这只会在行的开头起作用。)
由于上述原因,在找不到换行符的情况下,使用函数feof检查流的文件结束指示符可能是合适的,并在设置了流的文件结束指示符时忽略缺少的换行符。仅当如果打印出一条错误消息,提示该行太长,无法放入缓冲区,则不设置文件指示符。
为了逐行读取文本文件并确保始终读取整行,我推荐以下代码:

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

FILE *fp;
char line[512], *p;

int main()
{
    //open file
    fp = fopen( "input.txt", "r" );
    if ( fp == NULL )
    {
        fprintf( stderr, "Error opening file!\n" );
        exit( EXIT_FAILURE );
    }

    //read one line per loop iteration
    for (;;) //infinite loop, equivalent to while(1)
    {
        //attempt to read one line of input
        if ( fgets( line, sizeof line, fp ) == NULL )
        {
            //check for stream error
            if ( ferror(fp) )
            {
                fprintf( stderr, "Stream error!\n" );
                exit( EXIT_FAILURE );
            }

            //we must have encountered end-of-file, so break out
            //of the infinite loop without an error message
            break;
        }

        //attempt to find newline character
        p = strchr( line, '\n' );

        if ( p == NULL )
        {
            //a missing newline character should be ignored on
            //end-of-file
            if ( !feof(fp) )
            {
                fprintf( stderr, "Line too long for buffer!\n" );
                exit( EXIT_FAILURE );
            }
        }
        else
        {
            //remove newline character
            *p = '\0';
        }

        //a full line was read, so print it
        puts( line );
    }

    //cleanup
    fclose( fp );
}
hvvq6cgz

hvvq6cgz2#

这并不是fgets()的失败;如果fgets失败并返回空指针,则控制流不会进入循环。
如果您正在阅读带有fgets的行,并且该行恰好长于参数2中传递给fgets()的大小,则需要处理整行检查。在这种情况下,fgets()返回传递的缓冲区,并且是“成功”。
你可以通过检查字符串中的最后一个字符是否是换行符来检查这个问题,然后你可以中止或者以某种方式处理它。
这样的东西会处理检查:

#include <stdio.h>
#include <string.h>
#define LINE_MAX 5
int main() {
  char line[LINE_MAX + 1];
  while (fgets(line, LINE_MAX + 1, stdin) != NULL) {
    size_t length = strlen(line);
    if (length && line[length - 1] != '\n' && !feof(stdin)) {
      printf("line max (%d) reached\n", LINE_MAX);
      return 1;
    }
  }
}
lmvvr0a8

lmvvr0a83#

如何验证fgets()是否读取一行并处理错误(?)
回想一下C是如何从文本文件中定义 line 的:
文本流是一个有序的字符序列,组成 * 行 *,每行由零个或多个字符加上一个终止换行符组成。最后一行是否需要终止换行符是实现定义的。
很多案子容易处理。所有案子都难处理。

// Handles most
char buffer[4096];
while (fgets(buffer, sizeof buffer, file__1)) {
  fprintf(file__2, "%s", buffer);
}
if (feof(file__1)) {
  printf("End-of-file detected\n");
} else if (ferror(file__1)) {
  printf("Input error detected\n");
}

没有处理好的:

  • 线长度为sizeof buffer或更大。
  • 输入包含一个嵌入的 *null字符 *,因此fprintf(file__2, "%s", buffer);无法打印整行 *。
  • 当文件的最后一行缺少'\n'时,后面的一些代码可能会遇到麻烦,比如*strchr(buffer, '\n') = 0;,以删除 * 潜在的 * 尾随'\n'
  • 阅读不使用本地行尾字符的 text 文件可能无法很好地转换。
  • fgets()无法阅读使用宽字符的 text 文件。
  • 当调用fgets()ferror()已经为true时,fgets()可能会正常工作并返回非NULL。最好不要测试ferror(),除非fgets()返回NULL,然后先测试feof()
  • 缓冲区大小为1或大于INT_MAX
  • 病理尺寸参数为0或阴性时通过。
  • 长度为255或更长的行可能违反 * 环境限制 *。请参阅BUFSIZ详细信息。

额外的代码可以处理其中的一些。不幸的是,fgets()不够健壮。

相关问题