我该如何在C中逐行读取文件?

时间:2013-01-15 08:48:36

标签: macos unicode utf-8 getline fgets

我想逐行阅读文件。我fgets()工作正常,但如果一行超过我传递给fgets()的缓冲区大小,我不知道该怎么办?此外,由于fgets()似乎不支持Unicode,并且我想允许UTF-8文件,它可能会错过行结尾并读取整个文件,不是吗?

然后我想我会使用getline()。但是,我在Mac OS X上,虽然在getline()中指定了/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk/usr/include/stdio.h,但它不在/usr/include/stdio中,因此gcc在shell中找不到它。显然,它并不是特别便携,而且我希望我正在开发的库通常很有用。

那么在C中逐行读取文件的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

首先,您不太可能担心像U+2028这样的非标准行终结符。普通文本文件不应包含它们,并且绝大多数读取普通文本文件的现有软件都不支持它们。你提到了glibc中提供的getline(),但是没有提到MacOS的libc,如果getline()支持这种奇特的行终止符,我会感到惊讶。几乎可以肯定的是,你只需支持LF(U + 000A),也可以支持CR + LF(U + 000D U + 000A)。要做到这一点,您不需要关心UTF-8。这是UTF-8的ASCII兼容性的美感,并且是设计的。

对于比传递给fgets()的缓冲区更长的支持行,你可以在fgets周围添加一些额外的逻辑。在伪代码中:

while true {
    fgets(buffer, size, stream);
    dynamically_allocated_string = strdup(buffer);
    while the last char (before the terminating NUL) in the buffer is not '\n' {
        concatenate the contents of buffer to the dynamically allocated string
        /* the current line is not finished. read more of it */
        fgets(buffer, size, stream);
    }
    process the whole line, as found in the dynamically allocated string
}

但是再一次,我认为你会发现那里有很多软件根本没有那么麻烦,从解析系统配置文件(如/etc/passwd)到(某些)脚本语言的软件。根据您的使用情况,使用“足够大”的缓冲区(例如4096字节)并声明您不支持超过该行的行可能非常好。您甚至可以将其称为安全功能(线路长度限制可防止来自精心设计的输入文件的资源耗尽攻击)。

答案 1 :(得分:0)

基于this answer,这就是我的想法:

#define LINE_BUF_SIZE 1024

char * getline_from(FILE *fp) {
    char * line = malloc(LINE_BUF_SIZE), * linep = line;
    size_t lenmax = LINE_BUF_SIZE, len = lenmax;
    int c;

    if(line == NULL)
        return NULL;

    for(;;) {
        c = fgetc(fp);
        if(c == EOF)
            break;

        if(--len == 0) {
            len = lenmax;
            char * linen = realloc(linep, lenmax *= 2);

            if(linen == NULL) {
                // Fail.
                free(linep);
                return NULL;
            }
            line = linen + (line - linep);
            linep = linen;
        }

        if((*line++ = c) == '\n')
            break;
    }
    *line = '\0';
    return linep;
}

阅读stdin

char *line;
while ( line = getline_from(stdin) ) {
    // do stuff
    free(line);
}

要阅读其他文件,我首先使用fopen()打开它:

FILE *fp;
fp = fopen ( filename, "rb" );
if (!fp) {
    fprintf(stderr, "Cannot open %s: ", argv[1]);
    perror(NULL);
    exit(1);
}

char *line;
while ( line = getline_from(fp) ) {
    // do stuff
    free(line);
}

这对我很有用。我很乐意看到使用fgets()作为suggested by @paul-tomblin的替代方案,但我没有精力在今晚解决这个问题。