【问题标题】:Ruby parsing gzip binary stringRuby解析gzip二进制字符串
【发布时间】:2023-04-04 10:29:01
【问题描述】:

我有一个二进制字符串,其中包含两个串联的 gzip 二进制文件。 (我正在读取一个将两个 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

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

【问题讨论】:

  • 首先,拥有您正在使用的实际代码会有所帮助,而不是一些近似值。其次,你是如何解压缩抓取的数据的?
  • @FrederickCheung 他正在通过 GzipReader 解压缩。而这段代码很可能是他的实际代码,只是没有不必要和混乱的业务逻辑。

标签: ruby gzip


【解决方案1】:

gzip 格式使用包含先前压缩数据的校验和的页脚。一旦到达页脚,相同的 gzip 数据流就不能再有任何数据了。

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

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

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

【讨论】:

  • 我没有写日志文件。我只是想读它。我想解压缩已连接的两个 gz。我想避免重新创建您链接到的问题所在的第三个 gz。
  • @Tihom:根据en.wikipedia.org/wiki/Gzip,连接多个 GZIP 文件是完全有效的:“尽管它的文件格式还允许连接多个这样的流(压缩文件被简单地解压缩连接,就好像它们是最初是一个文件),...”当然,这与压缩到一个 GZIP 存档中的文件不同。
  • 这个答案不正确。 RFC 1952 中的 gzip 规范明确指出,gzip 流可以“只是”连接以生成有效的 gzip 流,并且兼容的解压缩器必须解压缩所有这些流。
  • 仍然(至少在撰写答案时),Ruby 忽略了第一个流之后的任何尾随数据。
【解决方案2】:
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

【讨论】:

    【解决方案3】:

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

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

    # 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
    

    【讨论】:

      【解决方案4】:

      这是确保读取整个文件的正确方法。即使未使用可能为 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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-15
        • 1970-01-01
        • 2017-05-27
        • 1970-01-01
        • 2011-03-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多