【问题标题】:Using fgets and strtok() to read a text file -C使用 fgets 和 strtok() 读取文本文件 -C
【发布时间】:2021-11-10 00:04:15
【问题描述】:

我正在尝试使用 fgets() 从标准输入中逐行读取文本,并将文本存储在变量“text”中。但是,当我使用 strtok() 拆分单词时,它仅适用于几行,然后才终止。我应该改变什么让它贯穿整个文本?


#define WORD_BUFFER_SIZE 50
#define TEXT_SIZE 200

int main(void) {
    char stopWords[TEXT_SIZE][WORD_BUFFER_SIZE];
    char word[WORD_BUFFER_SIZE];
    int numberOfWords = 0;
  
    while(scanf("%s", word) == 1){
      if (strcmp(word, "====") == 0){
        break;
      }
      strcpy(stopWords[numberOfWords], word);
      numberOfWords++;
    }

  char *buffer = malloc(sizeof(WORD_BUFFER_SIZE)*TEXT_SIZE);
  char *text = malloc(sizeof(WORD_BUFFER_SIZE)*TEXT_SIZE);
  
  while(fgets(buffer, WORD_BUFFER_SIZE*TEXT_SIZE, stdin) != NULL){  
    strcat(text, buffer);
  }
  
  char *k;
  k = strtok(text, " ");
  while (k != NULL) {
    printf("%s\n", k);
    k = strtok(NULL, " ");
  }
  
}

【问题讨论】:

  • 请提供minimal reproducible example(包括函数main和所有#include#define指令)并准确指定输入、预期输出和实际输出。
  • 几行示例输入会很好。可能有更简单的方法。
  • 您希望sizeof(WORD_BUFFER_SIZE) 具有什么价值?它只是sizeof(int),在大多数平台上是4
  • 函数strcat 需要一个以空字符结尾的字符串作为其第一个参数。但是,在第一次调用该函数时,第一个参数指向的内存内容是不确定的。这导致了未定义的行为。因此,您应该在第一次调用 strcat 之前对其进行初始化。
  • @RandomDev:如果您无法提供真实的输入,请提供几行虚构的示例输入,以便我们至少可以测试我们的解决方案。请edit您的问题提供此输入。

标签: c fgets strtok


【解决方案1】:
char *buffer = malloc(sizeof(WORD_BUFFER_SIZE)*TEXT_SIZE);
char *text = malloc(sizeof(WORD_BUFFER_SIZE)*TEXT_SIZE);

sizeof(WORD_BUFFER_SIZE) 是一个常数,它是整数的大小。你的意思可能是WORD_BUFFER_SIZE * TEXT_SIZE。但是您可以找到文件大小并准确计算出您需要多少内存。

char *text = malloc(...)
strcat(text, buffer);

text 未初始化且没有空终止符。 strcat 需要知道text 的结尾。在使用strcat之前必须设置text[0] = '\0'(它不像strcpy

int main(void) 
{
    fseek(stdin, 0, SEEK_END);
    size_t filesize = ftell(stdin);
    rewind(stdin);
    if (filesize == 0)
    { printf("not using a file!\n"); return 0; }

    char word[1000] = { 0 };

    //while (scanf("%s", word) != 1)
    //    if (strcmp(word, "====") == 0)
    //        break;

    char* text = malloc(filesize + 1);
    if (!text)
        return 0;
    text[0] = '\0';
    while (fgets(word, sizeof(word), stdin) != NULL)
        strcat(text, word);

    char* k;
    k = strtok(text, " ");
    while (k != NULL) 
    {
        printf("%s\n", k);
        k = strtok(NULL, " ");
    }

    return 0;
}

【讨论】:

    【解决方案2】:

    根据您在 cmets 部分提供的信息,输入文本长度超过 800 字节。

    但是,在行中

    char *text = malloc(sizeof(WORD_BUFFER_SIZE)*TEXT_SIZE);
    

    相当于

    char *text = malloc(800);
    

    您只为 text 分配了 800 个字节作为存储空间。因此,您没有分配足够的空间来将整个输入存储到text。尝试存储超过 800 个字节将导致 buffer overflow,它调用 undefined behavior

    如果要将整个输入存储到text,则必须确保它足够大。

    但是,这可能不是必需的。根据您的要求,一次处理一行可能就足够了,如下所示:

    while( fgets( buffer, sizeof buffer, stdin ) != NULL )
    {
        char *k = strtok( buffer, " " );
    
        while ( k != NULL )
        {
            printf( "%s\n", k );
            k = strtok( NULL, " " );
        }
    }
    

    在这种情况下,您不需要数组text。您只需要数组buffer 来存储该行的当前内容。

    由于您没有提供任何示例输入,我无法测试上面的代码。


    编辑:根据您对这个答案的 cmets,您的主要问题似乎是当您不知道输入的长度时,如何从 stdin 读取所有输入并将其存储为字符串前进。

    一种常见的解决方案是分配一个初始缓冲区,并在每次填满时将其大小加倍。您可以为此使用函数realloc

    #include <stdio.h>
    #include <stdlib.h>
    
    int main( void )
    {
        char *buffer;
        size_t buffer_size = 1024;
        size_t input_size = 0;
    
        //allocate initial buffer
        buffer = malloc( buffer_size );
        if ( buffer == NULL )
        {
            fprintf( stderr, "allocation error!\n" );
            exit( EXIT_FAILURE );
        }
    
        //continuously fill the buffer with input, and
        //grow buffer as necessary
        for (;;) //infinite loop, equivalent to while(1)
        {
            //we must leave room for the terminating null character
            size_t to_read = buffer_size - input_size - 1;
            size_t ret;
    
            ret = fread( buffer + input_size, 1, to_read, stdin );
    
            input_size += ret;
    
            if ( ret != to_read )
            {
                //we have finished reading from input
                break;
            }
    
            //buffer was filled entirely (except for the space
            //reserved for the terminating null character), so
            //we must grow the buffer
            {
                void *temp;
    
                buffer_size *= 2;
                temp = realloc( buffer, buffer_size );
    
                if ( temp == NULL )
                {
                    fprintf( stderr, "allocation error!\n" );
                    exit( EXIT_FAILURE );
                }
    
                buffer = temp;
            }
        }
    
        //make sure that `fread` did not fail end due to
        //error (it should only end due to end-of-file)
        if ( ferror(stdin) )
        {
            fprintf( stderr, "input error!\n" );
            exit( EXIT_FAILURE );
        }
    
        //add terminating null character
        buffer[input_size++] = '\0';
    
        //shrink buffer to required size
        {
            void *temp;
    
            temp = realloc( buffer, input_size );
    
            if ( temp == NULL )
            {
                fprintf( stderr, "allocation error!\n" );
                exit( EXIT_FAILURE );
            }
    
            buffer = temp;
        }
    
        //the entire contents is now stored in "buffer" as a
        //string, and can be printed
        printf( "contents of buffer:\n%s\n", buffer );
    
        free( buffer );
    }
    

    上面的代码假定输入将在文件结束条件下终止,如果输入是从文件通过管道传输的,情况可能就是这种情况。

    再想一想,与其在代码中为整个文件设置一个大字符串,不如在代码中使用一个 char* 数组,每个字符串代表一行,例如lines[0] 将是第一行的字符串,lines[1] 将是第二行的字符串。这样,您可以轻松地使用strstr 来查找“====”分隔符,并在每一行上使用strchr 来查找各个单词,并且仍然将所有行保留在内存中以供进一步处理。

    在这种情况下,我不建议您使用strtok,因为该函数通过将分隔符替换为空字符来修改字符串,因此具有破坏性。如果您需要字符串进行进一步处理,正如您在 cmets 部分中所述,那么这可能不是您想要的。这就是为什么我建议您改用strchr

    如果在编译时已知合理的最大行数,则解决方案相当简单:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAX_LINE_LENGTH 1024
    #define MAX_LINES 1024
    
    int main( void )
    {
        char *lines[MAX_LINES];
        int num_lines = 0;
    
        char buffer[MAX_LINE_LENGTH];
    
        //read one line per loop iteration
        while ( fgets( buffer, sizeof buffer, stdin ) != NULL )
        {
            int line_length = strlen( buffer );
    
            //verify that entire line was read in
            if ( buffer[line_length-1] != '\n' )
            {
                //treat end-of file as equivalent to newline character
                if ( !feof( stdin ) )
                {
                    fprintf( stderr, "input line exceeds maximum line length!\n" );
                    exit( EXIT_FAILURE );
                }
            }
            else
            {
                //remove newline character from string
                buffer[--line_length] = '\0';
            }
    
            //allocate memory for new string and add to array
            lines[num_lines] = malloc( line_length + 1 );
    
            //verify that "malloc" succeeded
            if ( lines[num_lines] == NULL )
            {
                fprintf( stderr, "allocation error!\n" );
                exit( EXIT_FAILURE );
            }
    
            //copy line to newly allocated buffer
            strcpy( lines[num_lines], buffer );
    
            //increment counter
            num_lines++;
        }
    
        //All input lines have now been successfully read in, so
        //we can now do something with them.
    
        //handle one line per loop iteration
        for ( int i = 0; i < num_lines; i++ )
        {
            char *p, *q;
    
            //attempt to find the " ==== " marker
            p = strstr( lines[i], " ==== " );
            if ( p == NULL )
            {
                printf( "Warning: skipping line because unable to find \" ==== \".\n" );
                continue;
            }
    
            //skip the " ==== " marker
            p += 6;
    
            //split tokens on remainder of line using "strchr"
            while ( ( q = strchr( p, ' ') ) != NULL )
            {
                printf( "found token: %.*s\n", (int)(q-p), p );
                p = q + 1;
            }
    
            //output last token
            printf( "found token: %s\n", p );
        }
    
        //cleanup allocated memory
        for ( int i = 0; i < num_lines; i++ )
        {
            free( lines[i] );
        }
    }
    

    使用以下输入运行上述程序时

    first line before deliminator ==== first line after deliminator
    second line before deliminator ==== second line after deliminator
    

    它有以下输出:

    found token: first
    found token: line
    found token: after
    found token: deliminator
    found token: second
    found token: line
    found token: after
    found token: deliminator
    

    但是,如果在编译时没有已知的合理最大行数,那么数组lines 也必须设计为以与前面程序中的buffer 类似的方式增长。这同样适用于最大行长度。

    【讨论】:

    • 非常感谢您的澄清。但是,我还需要整个文本的缓冲区以供以后使用。这就是 char *text 的目的。
    • @RandomDev:那你必须保证text足够大。如果您事先不知道text 所需的大小,那么您可以做的一件事是在空间不足时使用realloc 调整它的大小。在任何情况下,您都不应越界写信给text,因为这会导致未定义的行为(它可能会导致您的程序崩溃或以某种方式出现异常)。
    • @RandomDev:根据您的评论,您的主要问题似乎是在您不知道长度时读取来自stdin 的所有输入并将其存储为字符串提前输入。因此,我为这个问题添加了一个解决方案
    • @RandomDev:再想一想,与其为整个文件设置一个大字符串,不如将char* 的数组用于单个字符串,每个字符串代表一行,例如lines[0] 将是第一行的字符串,lines[1] 将是第二行的字符串。这样,您可以轻松地在每一行上使用strtok。您的程序是否存在最大行数?如果没有,那么数组lines 也必须被编程为按需增长。
    • @RandomDev:我现在添加了第三段代码,它可能正是你想要的。
    猜你喜欢
    • 1970-01-01
    • 2013-02-08
    • 2019-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-22
    • 2015-09-06
    • 2015-06-23
    相关资源
    最近更新 更多