如何在Ruby中从下到上读取文件?

问题描述:

我一直在研究Rails应用程序的日志查看器,并发现我需要从底部到顶部读取大约200行日志文件,而不是默认的顶部到底部。如何在Ruby中从下到上读取文件?

日志文件可能会变得很大,所以我已经尝试并排除了IO.readlines(“log_file.log”)[ - 200 ..- 1]方法。

是否有任何其他方法可以在不需要插件或gem的情况下向后读取文件?

+0

重复的:?阅读在Ruby文件的最后n行(http://stackoverflow.com/questions/754494) – hippietrail 2012-11-05 17:51:56

唯一正确的方法来做到这一点,也适用于巨大的文件是从结尾读取n字节,直到你有你想要的行数。这实际上是Unix tail的工作原理。

一个例子实施IO#tail(n),返回最后n线作为Array

class IO 
    TAIL_BUF_LENGTH = 1 << 16 

    def tail(n) 
    return [] if n < 1 

    seek -TAIL_BUF_LENGTH, SEEK_END 

    buf = "" 
    while buf.count("\n") <= n 
     buf = read(TAIL_BUF_LENGTH) + buf 
     seek 2 * -TAIL_BUF_LENGTH, SEEK_CUR 
    end 

    buf.split("\n")[-n..-1] 
    end 
end 

的实施是一个有点天真,但快速的基准测试显示有什么可笑的差异这种简单的实现已经可以使(

      user  system  total  real 
f.readlines[-200..-1] 7.150000 1.150000 8.300000 ( 8.297671) 
f.tail(200)    0.000000 0.000000 0.000000 ( 0.000367) 

基准代码:

用)与 yes > yes.txt生成一个〜25MB文件测试
require "benchmark" 

FILE = "yes.txt" 

Benchmark.bmbm do |b| 
    b.report "f.readlines[-200..-1]" do 
    File.open(FILE) do |f| 
     f.readlines[-200..-1] 
    end 
    end 

    b.report "f.tail(200)" do 
    File.open(FILE) do |f| 
     f.tail(200) 
    end 
    end 
end 

当然,other implementations已经存在。我没有尝试过,所以我不能告诉你哪个是最好的。

+0

我想你的意思'TAIL_BUF_LENGTH = 2 ** 16'或'1 2010-06-11 18:06:09

+0

工程太棒了!与readlines相比,基准差异是疯狂的。 是否可以在结果数组中输出每一行的相应行号? 谢谢! – ericalli 2010-06-11 18:41:44

+0

@ two2twelve:不,它不是。整个练习的*全部目的*是从“从下到上”阅读文件。 (你的话,不是我的。)你怎么知道你是哪一行(从文件的顶部*开始计算的),如果你从*底部开始*?还是你的意思是从底部向上计数?在这种情况下,很容易:缓冲区中位于索引“i”的行是从底部开始的第n行。 – 2010-06-12 01:38:13

有一个模块Elif可用(Perl的端口File::ReadBackwards),它可以有效地逐行向后读取文件。

因为我太新来评论molf真棒的答案,我必须将其作为单独的答案发布。 我需要此功能来读取日志文件,而日志的最后部分包含字符串,我需要知道它已完成,我可以开始解析它。

因此处理小型文件对我来说至关重要(我可能会在日志很小的时候执行ping操作)。 所以我增强MOLF代码:

class IO 
    def tail(n) 
     return [] if n < 1 
     if File.size(self) < (1 << 16) 
      tail_buf_length = File.size(self) 
      return self.readlines.reverse[0..n-1] 
     else 
      tail_buf_length = 1 << 16 
     end 
     self.seek(-tail_buf_length,IO::SEEK_END) 
     out = "" 
     count = 0 
     while count <= n 
      buf  = self.read(tail_buf_length) 
      count += buf.count("\n") 
      out  += buf 
      # 2 * since the pointer is a the end , of the previous iteration 
      self.seek(2 * -tail_buf_length,IO::SEEK_CUR) 
     end 
     return out.split("\n")[-n..-1] 
    end 
end