在C中逐行读取文件主要使用Syscalls

时间:2015-10-13 15:20:43

标签: c

我正在尝试逐行读取和解析文件。 我只想使用简单的系统调用(readopenclose,...)而不是fgetsgetc因为我希望学习,一种方式,基本面。 (我在类似问题上看了一些答案,但他们都使用fgets等等。

这就是我现在所拥有的:我编写的一个函数,它将1024个字符存储在文件的缓冲区中。

int main(void) {
    const char *filename = "file.txt";
    int fd = open(filename, O_RDONLY);
    char *buffer = malloc(sizeof (char) * 1024); 

    read(fd, buffer, 1024);        
    printf("%s", buffer);
    close(fd);
    free(buffer);    
}

例如,如何在'\ n'停止? 我知道,一旦我知道要停在哪里,我可以使用lseek和正确的偏移来继续阅读我停止的文件。

我不希望将整个文件存储在缓冲区中,然后解析它。 我想在缓冲区中添加一行,然后解析该行并重新分配我的缓冲区并继续读取该文件。

我正在考虑这样的事情,但我觉得它已经过了很好的优化,并且不确定之后在哪里添加lseek

char *line = malloc(sizeof (char) * 1024);
read(fd, buffer, 1);
int i = 0;
    while(*buffer != '\n' && *buffer != '\0'){
        line[i] = *buffer;
        ++i;
        *buffer++;
        read(fd, buffer, 1); //Assuming i < 1024 and *buffer != NULL
    }


  /* lseek somewhere after, probably should make 2 for loops 
   ** One loop till file isn't completly read
   ** Another loop inside that checks if the end of the line is reached
   ** At the end of second loop lseek to where we left
   */

谢谢:)

编辑:澄清的标题。

3 个答案:

答案 0 :(得分:2)

如果您要使用read一次读取一行(fgetsgetline要执行的操作),则必须跟踪偏移量找到每个'\n'后,在文件中。然后,只需要一次读取一行,从当前的偏移开始下一个read

我理解希望能够使用低级功能以及fgetsgetline。您发现,您基本上最终会以fgetsgetline的形式重新编码(以效率较低的方式)。但这肯定是好学习。这是一个简短的例子:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFSZ 128

ssize_t readline (char *buf, size_t sz, char *fn, off_t *offset);

int main (int argc, char **argv) {

    if (argc < 2) return 1;

    char line[BUFSZ] = {0};
    off_t offset = 0;
    ssize_t len = 0;
    size_t i = 0;

    /* using open/read, read each line in file into 'line' */
    while ((len = readline (line, BUFSZ, argv[1], &offset)) != -1)
        printf (" line[%2zu] : %s (%zd chars)\n", i++, line, len);

    return 0;
}

/* read 'sz' bytes from file 'fn' beginning at file 'offset'
   storing all chars  in 'buf', where 'buf' is terminated at
   the first newline found. On success, returns number of
   characters read, -1 on error or EOF with 0 chars read.
 */
ssize_t readline (char *buf, size_t sz, char *fn, off_t *offset)
{
    int fd = open (fn, O_RDONLY);
    if (fd == -1) {
        fprintf (stderr, "%s() error: file open failed '%s'.\n",
                __func__, fn);
        return -1;
    }

    ssize_t nchr = 0;
    ssize_t idx = 0;
    char *p = NULL;

    /* position fd & read line */
    if ((nchr = lseek (fd, *offset, SEEK_SET)) != -1)
        nchr = read (fd, buf, sz);
    close (fd);

    if (nchr == -1) {   /* read error   */
        fprintf (stderr, "%s() error: read failure in '%s'.\n",
                __func__, fn);
        return nchr;
    }

    /* end of file - no chars read
    (not an error, but return -1 )*/
    if (nchr == 0) return -1;

    p = buf;    /* check each chacr */
    while (idx < nchr && *p != '\n') p++, idx++;
    *p = 0;

    if (idx == nchr) {  /* newline not found  */
        *offset += nchr;

        /* check file missing newline at end */
        return nchr < (ssize_t)sz ? nchr : 0;
    }

    *offset += idx + 1;

    return idx;
}

示例输入

以下数据文件是相同的,只是第二个数据文件在每行文本之间包含空白行

$ cat dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.

$ cat dat/captnjack2.txt
This is a tale

Of Captain Jack Sparrow

A Pirate So Brave

On the Seven Seas.

<强>输出

$ ./bin/readfile dat/captnjack.txt
 line[ 0] : This is a tale (14 chars)
 line[ 1] : Of Captain Jack Sparrow (23 chars)
 line[ 2] : A Pirate So Brave (17 chars)
 line[ 3] : On the Seven Seas. (18 chars)

$ ./bin/readfile dat/captnjack2.txt
 line[ 0] : This is a tale (14 chars)
 line[ 1] :  (0 chars)
 line[ 2] : Of Captain Jack Sparrow (23 chars)
 line[ 3] :  (0 chars)
 line[ 4] : A Pirate So Brave (17 chars)
 line[ 5] :  (0 chars)
 line[ 6] : On the Seven Seas. (18 chars)

答案 1 :(得分:2)

您实际上是在实施自己的fgets版本。通过与fgets数据结构关联的内部缓冲区,可以避免FILE*中不可搜索流的逐字符读取。

在内部,fgets使用一个函数来填充该缓冲区,使用&#34; raw&#34;输入输出例程。之后,fgets逐个字符地通过缓冲区来确定'\n'的位置(如果有的话)。最后,fgets将内容从内部缓冲区复制到用户提供的缓冲区中,如果有足够的空间,则将null结束。

为了重新创建这个逻辑,您需要定义自己的FILE - 如struct,其中包含指向缓冲区的指针和指示缓冲区内当前位置的指针。之后,您需要定义自己的fopen版本,该版本初始化缓冲区并将其返回给调用者。您还需要编写自己的fclose版本来释放缓冲区。完成所有这些后,您可以按照上述逻辑实施fgets

答案 2 :(得分:2)

char *buffer = malloc(sizeof (char) * 1024); 
read(fd, buffer, 1024);        
printf("%s", buffer);

上述代码中有几个错误。

首先,malloc不是系统调用(也不是perror(3) ....)。根据定义,sizeof(char)为1。如果您只想 使用系统调用(在syscalls(2)中列出),则需要使用mmap(2),并且您应该以多个页面大小请求虚拟内存(请参阅{{ 3}}或getpagesize(2) ....),通常(但不总是)4千字节。 如果你可以使用malloc,你应该对它的失败进行编码,你最好将获得的缓冲区归零,所以至少

const int bufsiz = 1024;
char*buffer = malloc(bufsiz);
if (!buffer) { perror("malloc"); exit(EXIT_FAILURE); };
memset(buffer, 0, bufsiz);

然后,更重要的是,sysconf(3)正在返回一个你总是使用的号码(至少不会失败):

ssize_t rdcnt = read(fd, buffer, bufsiz);
if (rdcnt<0) { perror("read"); exit(EXIT_FAILURE); };

如果rdcnt为正,您通常会增加一些指针(rdcnt个字节)。零计数表示文件结束。

最后,您的printf正在使用<stdio.h>,您可以使用read(2) 代替。如果使用printf,请记住它正在缓冲。要么使用\n结束格式,要么使用write(2)

如果使用printf,请确保以零字节结束字符串。可能是将bufsiz-1传递给您的read;因为我们之前将区域归零,所以我们肯定会有一个终止零字节。

顺便说一句,您可以研究fflush(3) free software的某些C standard library实施的源代码,例如musl-libcGNU libc

不要忘记编译所有警告和调试信息(gcc -Wall -Wextra -g),以使用调试器(gdb),也许valgrind&amp; strace(1)