0

我想逐行读取文件。我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 中逐行读取文件的最佳方法是什么?

4

2 回答 2

1

首先,您不太可能需要担心像U+2028这样的非标准行终止符。普通文本文件不应该包含它们,并且绝大多数现有的读取普通文本文件的软件都不支持它们。您提到getline()了哪些在 glibc 中可用,但在 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 字节)并声明您不支持更长的行可能就足够了。您甚至可以将其称为安全功能(行长限制是防止来自精心制作的输入文件的资源耗尽攻击)。

于 2013-01-15T18:37:43.347 回答
0

基于这个答案,这就是我想出的:

#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);
}

这对我来说非常有效。我很想看到@paul-tomblin 建议fgets()的替代方案,但我今晚没有精力弄清楚。

于 2013-01-25T08:18:54.143 回答