Мне нужно прочитать последние 25 строк из файла (для отображения самых последних записей журнала). Есть ли в Ruby возможность начинать с конца файла и читать его задом наперед?
Чтение последних n строк файла в Ruby?
Ответы (8)
Если в системе *nix с tail
, вы можете читерить так:
last_25_lines = `tail -n 25 whatever.txt`
Достаточно ли велик файл, чтобы не читать его целиком? Если нет, вы можете просто сделать
IO.readlines("file.log")[-25..-1]
Если он слишком большой, вам может понадобиться использовать IO#seek
для чтения с конца файла и продолжайте поиск в начале, пока не увидите 25 строк.
IO.readlines("file.log").last(25)
, который возвращает пустой массив в этом случае.
- person Rohit Banga; 24.01.2016
Для Ruby существует библиотека под названием File::Tail. Это может дать вам последние N строк файла, как и утилита tail UNIX.
Я предполагаю, что в версии tail для UNIX есть некоторая оптимизация поиска с такими тестами (проверено на текстовом файле чуть более 11 МБ):
[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 использует аналогичный метод.
Изменить:
для любопытства Пакса:
[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
все еще меньше секунды, но если файловых операций много, это имеет большое значение.
Улучшенная версия превосходного решения на основе поиска от 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
tail': undefined local variable or method
size'. Есть идеи, как это исправить?
- person earlyadopter; 05.06.2014
Я только что написал быструю реализацию с #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
tail': undefined method
count' для nil:NilClass (NoMethodError)
- person Istvan; 18.02.2013
Вот версия 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
Я не могу поручиться за Ruby, но большинство этих языков следуют идиоме файлового ввода-вывода C. Это означает, что нет другого способа сделать то, о чем вы просите, кроме поиска. Обычно для этого используется один из двух подходов.
- Начиная с начала файла и просматривая его весь, запоминая последние 25 строк. Затем, когда вы нажмете конец файла, распечатайте их.
- Аналогичный подход, но с попыткой сначала найти наиболее вероятное местоположение. Это означает поиск (например) конца файла минус 4000 символов, а затем выполнение именно того, что вы делали в первом подходе, с оговоркой, что, если вы не получили 25 строк, вам нужно выполнить резервное копирование и повторить попытку (например, до конца файла минус 5000 символов).
Второй способ — тот, который я предпочитаю, поскольку, если вы мудро выберете первое смещение, вам почти наверняка понадобится только одна попытка. Файлы журнала по-прежнему, как правило, имеют фиксированную максимальную длину строки (я думаю, что программисты все еще имеют склонность к файлам с 80 столбцами даже после того, как их полезность ухудшилась). Я склонен выбирать желаемое количество строк, умноженное на 132, в качестве смещения.
И при беглом взгляде на онлайн-документацию по Ruby кажется, что она действительно следует идиоме C. Вы бы использовали "ios.seek(25*-132,IO::SEEK_END)"
, если бы следовали моему совету, а затем читайте дальше.
Как насчет:
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 строк в памяти и отображение их. Но это была всего лишь альтернативная мысль.