在C中向后读取文本文件

时间:2013-02-12 13:59:12

标签: c text stream standard-library

在C中向后读取文件的最佳方法是什么?我知道一开始你可能会认为这没用,但大多数日志等都会在文件末尾附加最新的数据。我想从文件向后读取文本,将其缓冲为行 - 即

ABC
高清
GHI

应在行中读取 ghi def abc

到目前为止,我已经尝试过:

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

    void read_file(FILE *fileptr)
    {
        char currentchar = '\0';
        int size = 0;

        while( currentchar != '\n' )
        {
            currentchar = fgetc(fileptr); printf("%c\n", currentchar);
            fseek(fileptr, -2, SEEK_CUR);
            if( currentchar == '\n') { fseek(fileptr, -2, SEEK_CUR); break; }
            else size++;

        }
        char buffer[size]; fread(buffer, 1, size, fileptr);
        printf("Length: %d chars\n", size);
        printf("Buffer: %s\n", buffer);


    }


    int main(int argc, char *argv[])
    {
        if( argc < 2) { printf("Usage: backwards [filename]\n"); return 1; }

        FILE *fileptr = fopen(argv[1], "rb");
        if( fileptr == NULL ) { perror("Error:"); return 1; }

        fseek(fileptr, -1, SEEK_END); /* Seek to END of the file just before EOF */
        read_file(fileptr);


        return 0;


    }

试图简单地读取一行并缓冲它。对不起,我的代码很糟糕,我很困惑。我知道你通常会为整个文件分配内存,然后读入数据,但是对于经常更改的大文件,我认为最好直接读取(特别是如果我想在文件中搜索文本)。 / p>

提前致谢

*对不起忘了提到这将在Linux上使用,所以新行只是没有CR的NL。 *

5 个答案:

答案 0 :(得分:10)

您可以通过程序tac管道输入,类似于cat但是向后!

http://linux.die.net/man/1/tac

答案 1 :(得分:7)

我建议使用更便携(希望)的文件大小确定方式,因为fseek(binaryStream, offset, SEEK_END)无法保证正常工作。请参阅下面的代码。

我认为文件应该至少在内核级别进行最低限度的缓冲(例如,默认情况下每个文件至少缓冲一个块),因此搜索不应该产生大量的额外I / O,并且应该只在内部提升文件位置。如果默认缓冲不满意,您可以尝试使用setvbuf()来加速I / O.

#include <limits.h>
#include <string.h>
#include <stdio.h>

/* File must be open with 'b' in the mode parameter to fopen() */
long fsize(FILE* binaryStream)
{
  long ofs, ofs2;
  int result;

  if (fseek(binaryStream, 0, SEEK_SET) != 0 ||
      fgetc(binaryStream) == EOF)
    return 0;

  ofs = 1;

  while ((result = fseek(binaryStream, ofs, SEEK_SET)) == 0 &&
         (result = (fgetc(binaryStream) == EOF)) == 0 &&
         ofs <= LONG_MAX / 4 + 1)
    ofs *= 2;

  /* If the last seek failed, back up to the last successfully seekable offset */
  if (result != 0)
    ofs /= 2;

  for (ofs2 = ofs / 2; ofs2 != 0; ofs2 /= 2)
    if (fseek(binaryStream, ofs + ofs2, SEEK_SET) == 0 &&
        fgetc(binaryStream) != EOF)
      ofs += ofs2;

  /* Return -1 for files longer than LONG_MAX */
  if (ofs == LONG_MAX)
    return -1;

  return ofs + 1;
}

/* File must be open with 'b' in the mode parameter to fopen() */
/* Set file position to size of file before reading last line of file */
char* fgetsr(char* buf, int n, FILE* binaryStream)
{
  long fpos;
  int cpos;
  int first = 1;

  if (n <= 1 || (fpos = ftell(binaryStream)) == -1 || fpos == 0)
    return NULL;

  cpos = n - 1;
  buf[cpos] = '\0';

  for (;;)
  {
    int c;

    if (fseek(binaryStream, --fpos, SEEK_SET) != 0 ||
        (c = fgetc(binaryStream)) == EOF)
      return NULL;

    if (c == '\n' && first == 0) /* accept at most one '\n' */
      break;
    first = 0;

    if (c != '\r') /* ignore DOS/Windows '\r' */
    {
      unsigned char ch = c;
      if (cpos == 0)
      {
        memmove(buf + 1, buf, n - 2);
        ++cpos;
      }
      memcpy(buf + --cpos, &ch, 1);
    }

    if (fpos == 0)
    {
      fseek(binaryStream, 0, SEEK_SET);
      break;
    }
  }

  memmove(buf, buf + cpos, n - cpos);

  return buf;
}

int main(int argc, char* argv[])
{
  FILE* f;
  long sz;

  if (argc < 2)
  {
    printf("filename parameter required\n");
    return -1;
  }

  if ((f = fopen(argv[1], "rb")) == NULL)
  {
    printf("failed to open file \'%s\'\n", argv[1]);
    return -1;
  }

  sz = fsize(f);
//  printf("file size: %ld\n", sz);

  if (sz > 0)
  {
    char buf[256];
    fseek(f, sz, SEEK_SET);
    while (fgetsr(buf, sizeof(buf), f) != NULL)
      printf("%s", buf);
  }

  fclose(f);
  return 0;
}

我只在带有2个不同编译器的Windows上测试过这个。

答案 2 :(得分:4)

有很多方法可以做到这一点,但一次读取一个字节肯定是较差的选择之一。

读取最后一个,比如4KB,然后从最后一个字符向上走回到上一个换行符将是我的选择。

另一个选项是mmap文件,只是假装文件是一块内存,然后向后扫描。 [你可以告诉mmap你正在向后阅读,以便为你预取数据]。

如果文件非常大(几千兆字节),您可能只想在mmap中使用该文件的一小部分。

答案 3 :(得分:1)

如果您想学习如何操作,请参阅Debian / Ubuntu示例(对于其他类似RPM的发行版,根据需要进行调整):

~$ which tac
/usr/bin/tac
~$ dpkg -S /usr/bin/tac
coreutils: /usr/bin/tac
~$ mkdir srcs
~$ cd srcs
~/srcs$ apt-get source coreutils

(clip apt-get output)

~/srcs$ ls
coreutils-8.13  coreutils_8.13-3.2ubuntu2.1.diff.gz  coreutils_8.13-3.2ubuntu2.1.dsc  coreutils_8.13.orig.tar.gz
~/srcs$ cd coreutils-8.13/
~/srcs/coreutils-8.13$ find . -name tac.c
./src/tac.c
~/srcs/coreutils-8.13$ less src/tac.c

这不是太长,有点超过600行,虽然它包含了一些高级功能,并且使用了其他来源的功能,但反向线路缓冲实现似乎在tac.c源文件中

答案 4 :(得分:0)

每个字节的FSEEKing听起来很慢。

如果您有内存,只需将整个文件读入内存,然后将其反转或向后扫描。

另一种选择是Windows内存映射文件。