Читая последние n строк файла в Ruby? - PullRequest
30 голосов
/ 16 апреля 2009

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

Ответы [ 8 ]

36 голосов
/ 16 апреля 2009

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

last_25_lines = `tail -n 25 whatever.txt`
25 голосов
/ 16 апреля 2009

Достаточно ли велик файл, чтобы вам не пришлось читать его целиком? Если нет, вы могли бы просто сделать

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

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

13 голосов
/ 16 апреля 2009

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

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

[john@awesome]$du -sh 11M.txt
11M     11M.txt
[john@awesome]$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:

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

[john@awesome]$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

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

8 голосов
/ 19 апреля 2012

Улучшенная версия отличного решения для поиска от 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
7 голосов
/ 17 сентября 2010

Я только что написал быстрое внедрение с #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 голосов
/ 29 января 2015

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

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

4 голосов
/ 16 апреля 2009

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

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

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

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

0 голосов
/ 16 апреля 2009

Как насчет:

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 строк в памяти и их отображение. Но это была просто альтернативная мысль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...