Rubyでファイルの末尾n行を表示するtailっぽい何かを作った
をまねてみる。
シンプル版
tail_simple.rb
file_path = ARGV[0]
row_limit = ARGV[1].to_i
buffer = []
File.foreach(file_path) do |line|
buffer << line
buffer.shift if buffer.size > row_limit
end
puts buffer
まず、元の記事のコメント欄より。Rubyでやるならこれでいい気がする。この特徴は
- シンプル
- ファイルを全部読む
- メモリ使用量は表示する分
など。試してみると意外と早い。
バッファー版
tail_buffered.rb
def reverse_chunks(file, size)
n = file.size / size
n -= 1 if file.size == n * size
len = file.size - n * size
until n < 0
file.seek(n * size)
yield file.read(len)
n -= 1
len = size
end
end
def offset_of_nth_chr_from_tail(file, count, target)
offset = 0
reverse_chunks(file, 1024*16) do |chunk|
chunk.size.times do |i|
chr = chunk[chunk.size - i - 1]
if chr == target || (offset == 0 && i == 0 && chr != target)
count -= 1
if count < 0
offset += i
return offset
end
end
end
offset += chunk.size
end
offset
end
def tail(fname, n_lines)
open(fname) do |file|
offset = offset_of_nth_chr_from_tail(file, n_lines, "\n")
file.seek(file.size - offset)
print file.read
end
end
tail(ARGV[0], ARGV[1].to_i)
次に自分で書いたRubyによるtail。ファイルをある程度のサイズの塊に分割して、後ろの塊のその後ろから改行の数を数える方式。
- 実装が複雑
- メモリー使用量は塊のサイズ
- 後ろから読むのででかいファイルも大丈夫
あんまり早くない。
mmap版
次はtailを書けって言われたらこの方向にするだろうという実装。
tail_mmap.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
void tail(char *fname, int n_lines) {
int fd = open(fname, O_RDONLY);
struct stat st;
fstat(fd, &st);
int fsize = st.st_size;
char *map = mmap(0, fsize, PROT_READ, MAP_PRIVATE, fd, 0);
char *cursor = map+fsize;
if (fsize > 0 && cursor[-1] != '\n') n_lines--;
for (int i = 0; i < fsize; i++){
cursor--;
if (*cursor == '\n') n_lines--;
if (n_lines < 0){
cursor++;
break;
}
}
write(1, cursor, fsize-(cursor-map));
munmap(map, fsize);
close(fd);
}
int main(int argc, char *argv[]){
if(argc != 3) exit(EXIT_FAILURE);
tail(argv[1], atoi(argv[2]));
exit(EXIT_SUCCESS);
}
mmap使って普通に後ろから数える方式。
- 割とシンプル
- mmapが環境依存
- mmapのメモリ使用量はどうなるんだろう?多くはないと思う
- 後ろから読むのででかいファイルも大丈夫
とメリットが多い。あとすごい早い。オリジナルのtailより早い。