Ruby解析gzip二进制字符串

时间:2012-01-10 15:49:47

标签: ruby gzip

我有一个二进制字符串,它包含两个连接的gzip binarys。 (我正在读取将两个gzip文件连接在一起的二进制文件日志文件)

换句话说,我有等同于:

require 'zlib'
require 'stringio'

File.open('t1.gz', 'w') do |f|
  gz = Zlib::GzipWriter.new(f)
  gz.write 'part one'
  gz.close
end

File.open('t2.gz', 'w') do |f|
  gz = Zlib::GzipWriter.new(f)
  gz.write 'part 2'
  gz.close
end


contents1 = File.open('t1.gz', "rb") {|io| io.read }
contents2 = File.open('t2.gz', "rb") {|io| io.read }

c = contents1 + contents2

gz = Zlib::GzipReader.new(StringIO.new(c))

gz.each do | l |
    puts l
end

当我尝试解压缩组合字符串时,我只得到第一个字符串。我如何获得两个字符串?

4 个答案:

答案 0 :(得分:3)

while c
  io = StringIO.new(c)
  gz = Zlib::GzipReader.new(io)
  gz.each do | l |
    puts l
  end
  c = gz.unused   # take unprocessed portion of the string as the next archive
end

请参阅ruby-doc

答案 1 :(得分:1)

gzip格式使用页脚,其中包含以前压缩数据的校验和。一旦到达页脚,就不会有更多数据用于相同的gziped数据流。

似乎Ruby Gzip阅读器刚刚在第一个遇到的页脚之后完成阅读,这在技术上是正确的,尽管如果还有更多数据,许多其他实现会引发错误。我真的不知道Ruby的确切行为。

关键是,您不能只连接原始字节流并期望工作正常。您必须实际调整流并重写页眉和页脚。有关详细信息,请参阅this question

或者您可以解压缩流,连接它们并重新压缩它,但这显然会产生一些开销......

答案 2 :(得分:0)

接受的答案对我不起作用。这是我修改过的版本。请注意gz.unused的不同用法。

此外,您应该在finish实例上调用GzipReader以避免内存泄漏。

# gzcat-test.rb
require 'zlib'
require 'stringio'
require 'digest/sha1'

# gzip -c /usr/share/dict/web2 /usr/share/dict/web2a > web-cat.gz
io = File.open('web-cat.gz')
# or, if you don't care about memory usage:
# io = StringIO.new File.read 'web-cat.gz'

# these will be hashes: {orig_name: 'filename', data_arr: unpacked_lines}
entries=[]
loop do
  entries << {data_arr: []}
  # create a reader starting at io's current position
  gz = Zlib::GzipReader.new(io)
  entries.last[:orig_name] = gz.orig_name
  gz.each {|l| entries.last[:data_arr] << l }
  unused = gz.unused  # save this before calling #finish
  gz.finish

  if unused
    # Unused is not the entire remainder, but only part of it.
    # We need to back up since we've moved past the start of the next entry.
    io.pos -= unused.size
  else
    break
  end
end

io.close

# verify the data
entries.each do |entry_hash|
  p entry_hash[:orig_name]
  puts Digest::SHA1.hexdigest(entry_hash[:data_arr].join)
end

执行命令

> ./gzcat-test.rb
web2"
a62edf8685920f7d5a95113020631cdebd18a185
"web2a"
b0870457df2b8cae06a88657a198d9b52f8e2b0a

我们的解压缩内容与原件匹配:

> shasum /usr/share/dict/web*
a62edf8685920f7d5a95113020631cdebd18a185  /usr/share/dict/web2
b0870457df2b8cae06a88657a198d9b52f8e2b0a  /usr/share/dict/web2a

答案 3 :(得分:0)

这是确保读取整个文件的正确方法。即使未使用可能是nil也不意味着已经到达了原始gzip压缩文件的末尾。

File.open(path_to_file) do |file|
  loop do
    gz = Zlib::GzipReader.new file
    puts gz.read

    unused = gz.unused
    gz.finish

    adjust = unused.nil? ? 0 : unused.length
    file.pos -= adjust
    break if file.pos == file.size
  end
end