Как перебирать 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