web-dev-qa-db-ja.com

Cでテキストファイルを逆方向​​に読み取る

Cでファイルを逆方向​​に読み取るための最良の方法は何ですか?最初は、これはまったく役に立たないと思われるかもしれませんが、ほとんどのログなどは、ファイルの最後に最新のデータを追加します。ファイルからテキストを逆方向に読み、行にバッファリングしたい-つまり

abc
def
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;


    }

単に1行を読み取って、それをバッファリングしようとしています。私のコードがひどいことを残念に思います、私はとても混乱しています。通常はファイル全体にメモリを割り当ててからデータを読み込むことは知っていますが、絶えず変化する大きなファイルの場合は、直接読み取る方がよいと思いました(特にファイル内のテキストを検索する場合)。

前もって感謝します

*これはLinuxで使用されることを忘れて申し訳ありません。したがって、改行はCRなしのNLです。*

13
Joshun

fseek(binaryStream, offset, SEEK_END)の動作は保証されていないため、ファイルサイズを決定するためのより移植性の高い(うまくいけば)方法をお勧めします。以下のコードを参照してください。

ファイルはカーネルレベルで少なくとも最小限にバッファリングする必要があると思います(たとえば、デフォルトでファイルごとに少なくとも1つのブロックをバッファリングする)ので、シークは大量の余分な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でのみテストしました。

7
Alexey Frunze

プログラムtacを介して入力をパイプすることができます。これはcatに似ていますが、逆方向です。

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

10
John Zwinck

これを行うにはかなりの数の方法がありますが、一度に1バイトを読み取ることは、間違いなく悪い選択の1つです。

最後の、たとえば4KBを読んでから、最後の文字から前の改行まで戻るのが私の選択です。

もう1つのオプションは、ファイルをmmapし、ファイルがメモリの塊であると偽って、その中で逆方向にスキャンすることです。 [データをプリフェッチするために、mmapを逆方向に読んでいることもわかります]。

ファイルが非常に大きい(数ギガバイト)場合は、mmap内のファイルのごく一部のみを使用することをお勧めします。

4
Mats Petersson

それを行う方法を学びたい場合は、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出力)

~/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 ソースファイル。

1
hyde

すべてのバイトのFSEEKingは非常に遅いように聞こえます。

メモリがある場合は、ファイル全体をメモリに読み込んで、逆にするか、逆方向にスキャンします。

もう1つのオプションは、Windowsのメモリマップファイルです。

0
Anonymouse