Мне нужно прочитать последние 25 строк из файла (для отображения последних записей журнала). Нужно ли в Ruby начинать в конце файла и читать его назад?
Чтение последних n строк файла в Ruby?
Ответ 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
Ответ 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 строк в памяти и их отображение. Но это была просто альтернативная мысль.