Как перебирать zip-файл в памяти в Ruby
Я пишу модульный тест, и один из них возвращает zip-файл, и я хочу проверить содержимое этого zip-файла, захватить некоторые значения из него и передать значения следующим тестам.
Я использую тест стойки, поэтому я знаю, что содержимое моего zip-файла находится внутри last_response.body
. Я просмотрел документацию RubyZip но кажется, что он всегда ожидал файла. Поскольку я запускаю модульный тест, я предпочитаю, чтобы все было сделано в памяти, а не загрязните любую папку тестовыми zip-файлами, если это возможно.
8 ответов
посмотреть @Бронсона!--10--> для более актуальной версии этого ответа с использованием нового API RubyZip.
документы Rubyzip, которые вы связали, выглядят немного старыми. The последняя версия (0.9.9) может обрабатывать IO
объекты, поэтому вы можете использовать StringIO (с небольшой настройкой).
даже если api примет IO
, он по-прежнему предполагает, что это файл и пытается вызвать path
на нем, так что первый патч обезьяна StringIO
добавить path
метод (на самом деле ничего не нужно делать):
require 'stringio'
class StringIO
def path
end
end
затем вы можете сделать что-то вроде:
require 'zip/zip'
Zip::ZipInputStream.open_buffer(StringIO.new(last_response.body)) do |io|
while (entry = io.get_next_entry)
# deal with your zip contents here, e.g.
puts "Contents of #{entry.name}: '#{io.read}'"
end
end
и все будет сделано в память.
ответ Мэтта совершенно правильный. Здесь он обновляется до нового API:
Zip::InputStream.open(StringIO.new(input)) do |io|
while entry = io.get_next_entry
if entry.name == 'doc.kml'
parse_kml(io.read)
else
raise "unknown entry in kmz file: #{entry.name}"
end
end
end
и больше нет необходимости в monkeypatch StringIO. Прогресс!
Zip::File.open_buffer(content) do |zip|
zip.each do |entry|
decompressed_data += entry.get_input_stream.read
end
end
можно использовать Tempfile
сбросить zip-файл во временный файл. Tempfile создает временный файл, специфичный для операционной системы, который будет очищен ОС после завершения вашей программы.
С RubyZip версия 1.2.1
(или, может быть, некоторые предыдущие версии тоже), нам просто нужно использовать open_buffer
метод Zip::File
класса.
из документации RubyZip:
как #open, но читает содержимое zip-архива из строки или открытого потока ввода-вывода и выводит данные в буфер. (Это можно использовать для извлечения данных из загруженного zip-архива без предварительного сохранения его на диск.)
пример:
Zip::File.open_buffer(last_response.body) do |zip|
zip.each do |entry|
puts entry.name
# Do whatever you want with the content files.
end
end
просто обновление на этом из-за изменений в rubyzip:
Zip::InputStream.open(StringIO.new(zip_file)) do |io|
while (entry = io.get_next_entry)
# deal with your zip contents here, e.g.
puts "Contents of #{entry.name}: '#{io.read}'"
end
end
вдохновленный ответом Мэтта у меня есть немного измененное решение для тех, кто должен использовать 0.9.х rubyzip камень. Мой не требует нового определения класса.
sio = StringIO.new(response.body)
sio.define_singleton_method(:path) {} #needed to create fake method path TO satisfy the ancient rubyzip 0.9.8 gem
Zip::ZipInputStream::open_buffer(sio) { |io|
while (entry = io.get_next_entry)
puts "Contents of #{entry.name}"
end
}
это работает для меня. В моем случае у меня есть только один файл, поэтому я использовал фиксированный путь, но вы можете использовать entry.name
построить свой путь.
input = HTTParty.get(link).body
Zip::File.open_buffer(input) do |zip_file|
zip_file.each do |entry|
entry.extract(path)
end
end