逐行读取文件,包括多个换行符

时间:2016-10-08 16:11:33

标签: c string file

我正在尝试逐行读取未知大小的文件,包括单个或多个换行符。 例如,如果我的sample.txt文件看起来像这样

abc   cd er  dj
text

more   text


zxc cnvx

我希望我的字符串看起来像这样

string1 = "abc   cd er  dj\n";
string2 = "text\n\n";
string3 = "more   text\n\n\n";
string4 = "zxc convex";

我似乎无法提出正常运行的解决方案。我已经尝试使用以下代码来获取每行的长度,包括换行符,但它给出了不正确的长度

while((temp = fgetc(input)) != EOF) {
    if (temp != '\n') {
        length++;
    }
    else {
        if (temp == '\n') {
            while ((temp = fgetc(input)) == '\n') {
                length++;
            }
        }
        length = 0;
    } 
}

我在想,如果我可以获得每行的长度,包括换行符,然后我可以使用该长度的malloc字符串,然后使用fread读取该字符串的大小,但我不确定这是否会起作用,因为我将必须移动文件指针以获取下一个字符串。

我也不想使用缓冲区,因为我不知道每行的长度。任何形式的帮助将不胜感激。

3 个答案:

答案 0 :(得分:1)

如果这些行很短并且没有很多行,您可以根据需要使用realloc重新分配内存。或者您可以使用更小(或更大)的块并重新分配。这有点浪费,但希望它最终能够平均化。

如果您只想使用一个分配,那么找到下一个非空行的开头并保存文件位置(使用ftell)。然后得到当前位置和前一个开始位置之间的差异,你知道要分配多少内存。对于读取是,你必须来回寻找,但如果它不是很大,所有数据都将在缓冲区中,它只是修改一些指针。阅读后,寻找保存的位置,使其成为下一个开始位置。

那么你当然可以memory-map the file。这会将文件内容放入您的内存映射中,就像它已全部分配一样。对于64位系统,地址空间足够大,因此您应该能够映射多千兆字节的文件。然后你不需要寻找或分配内存,你所做的只是操纵指针而不是寻求。阅读只是一个简单的内存复制(但是由于文件已经“存储”在内存中你已经不需要了,只需保存指针)。

对于fseekftell上的非常简单示例,这与您的问题有些相关,我为您整理了这个小程序。它并没有真正做任何特别的事情,但它展示了如何以一种可用于我上面讨论的第二种方法的原型的方式使用这些函数。

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

int main(void)
{
    FILE *file = fopen("some_text_file.txt", "r");

    // The position after a successful open call is always zero
    long start_of_line = 0;

    int ch;

    // Read characters until we reach the end of the file or there is an error
    while ((ch = fgetc(file)) != EOF)
    {
        // Hit the *first* newline (which differs from your problem)
        if (ch == '\n')
        {
            // Found the first newline, get the current position
            // Note that the current position is the position *after* the newly read newline
            long current_position = ftell(file);

            // Allocate enough memory for the whole line, including newline
            size_t bytes_in_line = current_position - start_of_line;
            char *current_line = malloc(bytes_in_line + 1);  // +1 for the string terminator

            // Now seek back to the start of the line
            fseek(file, start_of_line, SEEK_SET);  // SEEK_SET means the offset is from the beginning of the file

            // And read the line into the buffer we just allocated
            fread(current_line, 1, bytes_in_line, file);

            // Terminate the string
            current_line[bytes_in_line] = '\0';

            // At this point, if everything went well, the file position is
            // back at current_position, because the fread call advanced the position
            // This position is the start of the next line, so we use it
            start_of_line = current_position;

            // Then do something with the line...
            printf("Read a line: %s", current_line);

            // Finally free the memory we allocated
            free(current_line);
        }

        // Continue loop reading character, to read the next line
    }

    // Did we hit end of the file, or an error?
    if (feof(file))
    {
        // End of the file it is

        // Now here's the tricky bit. Because files doesn't have to terminated
        // with a newline, at this point we could actually have some data we
        // haven't read. That means we have to do the whole thing above with
        // the allocation, seeking and reading *again*

        // This is a good reason to extract that code into its own function so
        // you don't have to repeat it

        // I will not repeat the code my self. Creating a function containing it
        // and calling it is left as an exercise
    }

    fclose(file);

    return 0;
}

请注意,为简洁起见,程序不包含任何错误处理。还应该注意的是,我实际上并没有尝试该程序,甚至没有尝试编译它。这都是临时写的答案。

答案 1 :(得分:0)

你的长度错了。原因是在你进入循环之前:

while ((temp = fgetc(input)) == '\n')

您忘了增加length,因为它刚刚读了一个\n字符。所以那些线必须成为:

else {
    length++;                // add the \n just read
    if (temp == '\n') {      // this is a redundant check
        while ((temp = fgetc(input)) == '\n') {
            length++;
        }
        ungetc(temp, input);
    }

<小时/> EDIT

在阅读了第一个非\n后,您现在已经阅读了下一行的第一个字符,因此您必须取消它:

ungetc(temp, input);

答案 2 :(得分:0)

除非您尝试编写自己的实现,否则可以使用the standard POSIX getline() function

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


int main(void)
{
    FILE *fp;
    char *line = NULL;
    size_t len = 0;
    ssize_t read;
    fp = fopen("/etc/motd", "r");
    if (fp == NULL)
        exit(1);
    while ((read = getline(&line, &len, fp)) != -1) {
        printf("Retrieved line of length %zu :\n", read);
        printf("%s", line);
    }
    if (ferror(fp)) {
        /* handle error */
    }
    free(line);
    fclose(fp);
    return 0;
}