使用Ruby如何生成zip文件而无需保存到磁盘?

44

我已经在内存中生成了许多PDF文件,我想在将其作为电子邮件附件发送之前将它们压缩成一个zip文件。我查看了Rubyzip,但它似乎不允许我创建一个不保存到磁盘的zip文件(也许我错了)。

有没有办法在不创建临时文件的情况下压缩这些文件?


请参考 vas 的回答,它完全符合你的需求! - maerics
5个回答

63

我曾经遇到相似的问题,使用rubyzip gem和stringio对象解决了它。 事实证明rubyzip提供了一个返回stringio对象的方法:ZipOutputStream.write_buffer

你可以使用put_next_entry创建zip文件结构并写入内容,完成后可以倒回stringio并使用sysread读取二进制数据。

请参考以下简单示例(适用于rubyzip 0.9.X):

require 'zip/zip'
stringio = Zip::OutputStream.write_buffer do |zio|
  zio.put_next_entry("test.txt")
  zio.write "Hello world!"
end
stringio.rewind
binary_data = stringio.sysread

在jruby 1.6.5.1(ruby-1.9.2-p136)(2011-12-27 1bf37c2) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_29) [Windows Server 2008-amd64-java]上测试通过。

以下示例适用于rubyzip >= 1.0.0

require 'rubygems'    
require 'zip'
stringio = Zip::OutputStream.write_buffer do |zio|
  zio.put_next_entry("test.txt")
  zio.write "Hello world!"
end
binary_data = stringio.string

在 jruby 1.7.22 (1.9.3p551) 2015-08-20 c28f492 on OpenJDK 64-Bit Server VM 1.7.0_79-b14 +jit [linux-amd64] 上测试通过,并使用 rubyzip gem 1.1.7。


1
谢谢,它管用了。然而在我写这个问题的时候,这种方法还不存在。它是在2011-01-07添加的。感谢你的答案,下次我需要在内存中压缩时,我就知道该怎么做了。 - Martinos
1
LoadError 无法加载该文件 -- zip。 - Arnold Roa
1
以上代码适用于较旧版本的rubyzip gem。1.0版本更改了接口。请参见:https://github.com/rubyzip/rubyzip#important-note - vas
2
write_buffer现在是Zip::OutputStream::write_buffer。 - rkabir
另外,注意到在新版本中,require 'zip/zip' 不起作用。正确的语句是 require 'zip'。 - 3coins
显示剩余3条评论

5
Ruby提供了一个非常方便的StringIO库,可以用于将字符串作为输出IO对象或模拟由字符串支持的文件读取。
这里的挑战在于RubyZip不支持在创建Zip::ZipOutputStream时直接使用IO对象,但是如果查看initialize的实现,并根据您愿意尝试的程度,您可能能够扩展该类并允许它在构造函数中使用IO对象或文件名。

1

我找到了两个RubyZip库。

  1. Chilkat的Ruby Zip库
  2. Sourceforge上的rubyzip

Chilkat的库绝对允许我们在内存中创建zip文件,而不像这些链接中自动将其写入磁盘:Zip to Memory, Zip from in memory data

另一方面,在SourceForge上可能提供了在内存中压缩文件的选项,但我不是完全确定,因为我对ruby非常陌生。 SourceForge的rubyzip基于java.util.zip,这导致它有一个名为ZipOutputStream的类。我不知道rubyzip实现有多好,但使用java.util.zip实现,可以将OutputStream设置为ByteArrayOutputStreamFileOutputStreamFilterOutputStreamObjectOutputStreamOutputStreamPipedOutputStream等。
如果rubyzip实现也是如此,那么使用ZipOutputStream将某种类型的ByteArrayOutputStream传递进去就可以将其输出到内存中。

如果在rubyzip中不存在,那么你可以编写自己的实现,并提交给rubyzip进行开源包含。


我所提到的是这个 gem:http://rubygems.org/gems/rubyzip我不想使用 Chilkat 库,因为它不是开源的。 - Martinos
我不会给这个评分降级,但是我没有找到任何一个像“不,标准的Ruby ZIP类是做不到这一点的”这样的评论。我认为你应该从这个开始。 - dimitarvp
事实上,ZipOutputStream的新方法需要一个文件名作为参数,我没有找到任何传递IO对象的方法。 - Martinos
dimitko,Ruby 没有标准的 ZIP 库。我已经检查过 rubyzip,但似乎没有我想要做的东西。 - Martinos
1
@flutedemetan,就像我上面说的那样,如果它不存在,那么你要么需要使用另一个库,要么为rubyzip开源项目做出自己的贡献。 - mezoid

0

挂载需要超级用户权限。 - Abe Voelker
@AbeVoelker 是的,这个答案假设你已经有了一个tmpfs,或者有一个友好的本地系统管理员。 - user67416

0

接受的答案效果很好,但并没有解决我的问题。我不想使用 write_buffer方法,因为它在块关闭后自动关闭流。下面的代码片段让你更加控制流何时创建和关闭。

require 'stringio'
require 'zip'

io = StringIO.new
zip_io = Zip::OutputStream.new(io, true) # 'true' indicates 'io' is a stream
zip_io.put_next_entry('test.txt')
zip_io.write('Hello world!')

# Read the data and close the streams
io.rewind
binary_data = io.read
zip_io.close_buffer
io.close

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接