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

时间:2010-06-11 16:11:19

标签: ruby-on-rails ruby file-io

我一直在研究Rails应用程序的日志查看器,并且发现我需要从下到上阅读大约200行日志文件,而不是默认的从上到下。

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

有没有其他方法可以在Ruby中向后读取文件而无需插件或gem?

3 个答案:

答案 0 :(得分:17)

执行此操作的唯一正确方法也适用于大量文件,一次读取 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

实现有点天真,但快速基准测试显示了这个简单实现已经可以做出的荒谬差异(使用yes > yes.txt生成的~25MB文件进行测试):

                            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)

基准代码:

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已经存在。我没有尝试过,所以我不能告诉你哪个是最好的。

答案 1 :(得分:3)

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

答案 2 :(得分:1)

由于我太新,无法评论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