「Unix/Linuxプログラミング 理論と実践」読み進めています。
2章の最後の課題。自分でtailコマンドを実装せよ。
下記のように仕上げた。ファイルをオープンしてからfseekでいったん末尾に移動、その後後ろから改行コードを任意の数だけ検索する。
効率よく後ろ方向に検索する方法はもっと効率のよい方法がありそうだ。
今の知識ではlseekで一つ一つ戻っていく方法を思いついて、それで実装した。それなりに上手く動いている。
ただパフォーマンスとしては改善の余地がありそうである。
FILE*構造体を介しているから、アプリケーション側である程度バッファが利用される。
これはlseekで後ろ方向に移動するときも有効なのか?調べる必要がある。
また探索を行う際には一度getcでストリームから取得したものはungetcで戻しておく必要がある。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #define TAIL_LINE 10 #define BUF_SIZE 4096 int main(int argc, char * argv[]) { FILE * fp; int c; int from_bottom = 0; char buff[BUF_SIZE]; if (argc < 2) { printf("usage. mytail [file_name]\n"); exit(-1); } if ((fp = fopen(argv[1], "r")) == NULL) { perror("failed to open file."); exit(-1); } if ((fseek(fp, 0, SEEK_END) == -1) || (fseek(fp, -1, SEEK_CUR) == -1)) { printf("failed to seek."); exit(-1); } while(1) { if ((c = fgetc(fp)) == '\n') { from_bottom++; } if (c == EOF) { break; } if (TAIL_LINE <= from_bottom) { break; } if (ungetc(c, fp) == EOF){ printf("failed to ungetc."); exit(-1); } fseek(fp, -1, SEEK_CUR); } while (fgets(buff, BUF_SIZE, fp) != NULL) { printf("%s", buff); } printf("\n"); }
実行すると下記のようになる
$ ./a.out mytail.c exit(-1); } fseek(fp, -1, SEEK_CUR); } while (fgets(buff, BUF_SIZE, fp) != NULL) { printf("%s", buff); } printf("\n"); }
tailの実装と比較してみよう。
freebsdのリポジトリから実装を参照してみる。
下記に一部llinesという関数を抜粋する。
int lines(FILE *fp, const char *fn, off_t off) { struct { int blen; u_int len; char *l; } *llines; int ch, rc; char *p, *sp; int blen, cnt, recno, wrap; if ((llines = calloc(off, sizeof(*llines))) == NULL) err(1, "calloc"); p = sp = NULL; blen = cnt = recno = wrap = 0; rc = 0; while ((ch = getc(fp)) != EOF) { if (++cnt > blen) { if ((sp = realloc(sp, blen += 1024)) == NULL) err(1, "realloc"); p = sp + cnt - 1; } *p++ = ch; if (ch == '\n') { if ((int)llines[recno].blen < cnt) { llines[recno].blen = cnt + 256; if ((llines[recno].l = realloc(llines[recno].l, llines[recno].blen)) == NULL) err(1, "realloc"); } bcopy(sp, llines[recno].l, llines[recno].len = cnt); cnt = 0; p = sp; if (++recno == off) { wrap = 1; recno = 0; } } } if (ferror(fp)) { ierr(fn); rc = 1; goto done; } if (cnt) { llines[recno].l = sp; sp = NULL; llines[recno].len = cnt; if (++recno == off) { wrap = 1; recno = 0; } } if (rflag) { for (cnt = recno - 1; cnt >= 0; --cnt) WR(llines[cnt].l, llines[cnt].len); if (wrap) for (cnt = off - 1; cnt >= recno; --cnt) WR(llines[cnt].l, llines[cnt].len); } else { if (wrap) for (cnt = recno; cnt < off; ++cnt) WR(llines[cnt].l, llines[cnt].len); for (cnt = 0; cnt < recno; ++cnt) WR(llines[cnt].l, llines[cnt].len); } done: for (cnt = 0; cnt < off; cnt++) free(llines[cnt].l); free(sp); free(llines); return (rc); }
これを見ると引数として指定したファイルを最初から最後まで走査しているようである。
予め各行を表す構造体を定義し、tailで表示する行数だけメモリを確保する。
この構造体は循環リストのようになっており、例えば10行tailする際には11行目を読み込んだ時にははじめの1行目のデータを上書きしてメモリに保存する。
つまり指定行しか保持しないで、ファイルの末尾に達した時には自動的に最後のn行がメモリに残るような作りになっている。
あとは最後にデータを表示するだけである。
後ろからデータをseekするようなことはやっていなかった。
コメントを残す