Чтение текстового файла назад в C

Какой лучший способ прочитать файл назад в C? Я знаю, что сначала вы можете подумать, что это бесполезно, но большинство журналов и т.д. Добавляют самые последние данные в конце файла. Я хочу прочитать текст из файла назад, буферизируя его в строки - это

а
Защита
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;


    }

В попытке просто прочитать одну строку и буферизировать ее. Извините, что мой код ужасен, я очень запутался. Я знаю, что вы обычно выделяете память для всего файла, а затем читаете данные, но для больших файлов, которые постоянно меняются, я думал, что лучше читать напрямую (особенно если я хочу искать текст в файле).

Заранее спасибо

* Извините, забыл упомянуть, что это будет использоваться в Linux, поэтому новые строки - это просто NL без CR. *

Ответ 1

Я рекомендую более переносимый (надеюсь) способ определения размера файла, поскольку fseek(binaryStream, offset, SEEK_END) не гарантированно работает. См. Код ниже.

Я считаю, что файлы должны быть, по крайней мере, минимально буферизованы на уровне ядра (например, буферизация по крайней мере по одному блоку на файл по умолчанию), поэтому запросы не должны вызывать значительного объема дополнительных операций ввода-вывода и должны только продвигать положение файла внутри, Если буферизация по умолчанию не является удовлетворительной, вы можете попытаться использовать setvbuf() для ускорения ввода-вывода.

#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

Вы можете просто подключить вход через программу tac, которая похожа на cat, но назад!

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

Ответ 3

Существует несколько способов сделать это, но чтение байта за один раз - это, безусловно, один из худших вариантов.

Чтение последнего, скажем, 4 КБ, а затем переход от последнего символа к предыдущей новой строке будет моим выбором.

Другим вариантом является mmap файл, и просто притворяйтесь, что файл является куском памяти, и отсканируйте его назад. [Вы можете сказать mmap, что вы тоже читаете назад, чтобы сделать для вас предварительную выборку данных].

Если файл ОЧЕНЬ большой (несколько гигабайт), вы можете использовать только небольшую часть файла в mmap.

Ответ 4

Если вы хотите узнать, как это сделать, здесь пример 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

(клип 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.

Ответ 5

FSEEKing для каждого байтового звука БОЛЬШОЙ медленный.

Если у вас есть память, просто прочитайте весь файл в памяти и либо отмените его, либо сканируйте назад.

Другой вариант - файлы с отображением памяти Windows.