Подтвердить что ты не робот

Чтение последних n строк файла в Ruby?

Мне нужно прочитать последние 25 строк из файла (для отображения последних записей журнала). Нужно ли в Ruby начинать в конце файла и читать его назад?

4b9b3361

Ответ 1

Если в системе * nix с tail вы можете обманывать вот так:

last_25_lines = `tail -n 25 whatever.txt`

Ответ 2

Является ли файл достаточно большим, чтобы вам не читать все это? Если нет, вы можете просто сделать

IO.readlines("file.log")[-25..-1]

Если это значение велико, вам может понадобиться IO#seek, чтобы читать из конца файла и продолжать искать начало, пока вы не увидите 25 строк.

Ответ 3

Существует библиотека для Ruby, называемая File:: Tail. Это может дать вам последние N строк файла, как утилита хвоста UNIX.

Я предполагаю, что существует некоторая оптимизация поиска в версии хвоста UNIX с такими же эталонами тестов (протестировано в текстовом файле чуть более 11 М):

[[email protected]]$du -sh 11M.txt
11M     11M.txt
[[email protected]]$time tail -n 25 11M.txt
/sbin/ypbind
/sbin/arptables
/sbin/arptables-save
/sbin/change_console
/sbin/mount.vmhgfs
/misc
/csait
/csait/course
/.autofsck
/~
/usb
/cdrom
/homebk
/staff
/staff/faculty
/staff/faculty/darlinr
/staff/csadm
/staff/csadm/service_monitor.sh
/staff/csadm/.bash_history
/staff/csadm/mysql5
/staff/csadm/mysql5/MySQL-server-community-5.0.45-0.rhel5.i386.rpm
/staff/csadm/glibc-common-2.3.4-2.39.i386.rpm
/staff/csadm/glibc-2.3.4-2.39.i386.rpm
/staff/csadm/csunixdb.tgz
/staff/csadm/glibc-headers-2.3.4-2.39.i386.rpm

real    0m0.012s
user    0m0.000s
sys     0m0.010s

Я могу только представить, что библиотека Ruby использует аналогичный метод.

Edit:

для любопытства Пакса:

[[email protected]]$time cat 11M.txt | tail -n 25
/sbin/ypbind
/sbin/arptables
/sbin/arptables-save
/sbin/change_console
/sbin/mount.vmhgfs
/misc
/csait
/csait/course
/.autofsck
/~
/usb
/cdrom
/homebk
/staff
/staff/faculty
/staff/faculty/darlinr
/staff/csadm
/staff/csadm/service_monitor.sh
/staff/csadm/.bash_history
/staff/csadm/mysql5
/staff/csadm/mysql5/MySQL-server-community-5.0.45-0.rhel5.i386.rpm
/staff/csadm/glibc-common-2.3.4-2.39.i386.rpm
/staff/csadm/glibc-2.3.4-2.39.i386.rpm
/staff/csadm/csunixdb.tgz
/staff/csadm/glibc-headers-2.3.4-2.39.i386.rpm

real    0m0.350s
user    0m0.000s
sys     0m0.130s

все еще под вторым, но если есть много операций с файлами, это имеет большое значение.

Ответ 4

Я просто написал краткую реализацию с помощью #seek:

class File
  def tail(n)
    buffer = 1024
    idx = (size - buffer).abs
    chunks = []
    lines = 0

    begin
      seek(idx)
      chunk = read(buffer)
      lines += chunk.count("\n")
      chunks.unshift chunk
      idx -= buffer
    end while lines < n && pos != 0

    chunks.join.lines.reverse_each.take(n).reverse.join
  end
end

File.open('rpn-calculator.rb') do |f|
  p f.tail(10)
end

Ответ 5

Улучшенная версия превосходного поискового решения manveru. Это возвращает ровно n строк.

class File

  def tail(n)
    buffer = 1024
    idx = [size - buffer, 0].min
    chunks = []
    lines = 0

    begin
      seek(idx)
      chunk = read(buffer)
      lines += chunk.count("\n")
      chunks.unshift chunk
      idx -= buffer
    end while lines < ( n + 1 ) && pos != 0

    tail_of_file = chunks.join('')
    ary = tail_of_file.split(/\n/)
    lines_to_return = ary[ ary.size - n, ary.size - 1 ]

  end
end

Ответ 6

Я не могу ручаться за Ruby, но большинство этих языков следует за C-идиомой ввода-вывода файлов. Это означает, что нет способа делать то, что вы просите, кроме поиска. Обычно это занимает один из двух подходов.

  • Начиная с начала файла и просматривая все это, помня последние 25 строк. Затем, когда вы нажмете конец файла, распечатайте его.
  • Аналогичный подход, но сначала попытка поиска наилучшего предположения. Это означает поиск (например) конца файла минус 4000 символов, а затем выполнение именно того, что вы сделали в первом подходе, при условии, что если вы не получили 25 строк, вам нужно выполнить резервное копирование и повторить попытку (например, до конца файла минус 5000 символов).

Второй способ - это тот, который я предпочитаю, поскольку, если вы выбираете свое первое смещение разумно, вам почти наверняка понадобится только один выстрел в него. Файлы журналов по-прежнему имеют фиксированную максимальную длину строки (я думаю, что кодеры все еще имеют склонность к 80-колоновым файлам после того, как их полезность ухудшилась). Я склонен выбирать количество желаемых линий, умноженное на 132 как мое смещение.

И с беглого взгляда на рубиновые документы в Интернете, похоже, что он следит за C-идиомой. Вы бы использовали "ios.seek(25*-132,IO::SEEK_END)", если бы вы пошли за моим советом, а затем прочитали оттуда.

Ответ 7

Здесь версия хвоста, которая не хранит никаких буферов в памяти, пока вы идете, но вместо этого использует "указатели". Также проверяет привязку, поэтому вы не пытаетесь найти отрицательное смещение (если, например, вам нужно больше читать, но меньше размера вашего куска).

def tail(path, n)
  file = File.open(path, "r")
  buffer_s = 512
  line_count = 0
  file.seek(0, IO::SEEK_END)

  offset = file.pos # we start at the end

  while line_count <= n && offset > 0
    to_read = if (offset - buffer_s) < 0
                offset
              else
                buffer_s
              end

    file.seek(offset-to_read)
    data = file.read(to_read)

    data.reverse.each_char do |c|
      if line_count > n
        offset += 1
        break
      end
      offset -= 1
      if c == "\n"
        line_count += 1
      end
    end
  end

  file.seek(offset)
  data = file.read
end

в https://gist.github.com/shaiguitar/6d926587e98fc8a5e301

Ответ 8

Как насчет:

file = []
File.open("file.txt").each_line do |line|
  file << line
end

file.reverse.each_with_index do |line, index|
  puts line if index < 25
end

Производительность будет ужасной по сравнению с большим файлом, так как она повторяется дважды, лучшим подходом было бы уже упомянутое чтение файла и сохранение последних 25 строк в памяти и их отображение. Но это была просто альтернативная мысль.