Ruby-解压缩具有混合类型的数组

时间:2019-06-12 05:05:42

标签: ruby binaryfiles binary-data pack unpack

我正在尝试使用unpack来解码二进制文件。二进制文件具有以下结构:

ABCDEF\tFFFABCDEF\tFFFF....

其中

ABCDEF -> String of fixed length
\t -> tab character
FFF -> 3 Floats
.... -> repeat thousands of times

我知道当类型都相同或只有数字和固定长度的数组时该怎么做,但是在这种情况下我很挣扎。例如,如果我有一个浮动列表,我会这样做

s.unpack('F*')

或者如果我有整数和浮点数

[1, 3.4, 5.2, 4, 2.3, 7.8]

我会

s.unpack('CF2CF2')

但是在这种情况下,我有点迷路了。我希望使用带有括号的`(CF2)*'这样的格式字符串,但是它不起作用。

如果需要的话,我需要使用Ruby 2.0.0-p247

示例

ary = ["ABCDEF\t", 3.4, 5.6, 9.1, "FEDCBA\t", 2.5, 8.9, 3.1]
s = ary.pack('P7fffP7fff')

然后

s.scan(/.{19}/)
["\xA8lf\xF9\xD4\x7F\x00\x00\x9A\x99Y@33\xB3@\x9A\x99\x11", "A\x80lf\xF9\xD4\x7F\x00\x00\x00\x00 @ff\x0EAff"]

最后

s.scan(/.{19}/).map{ |item| item.unpack('P7fff') }
Error: #<ArgumentError: no associated pointer>
<main>:in `unpack'
<main>:in `block in <main>'
<main>:in `map'
<main>:in `<main>'

2 个答案:

答案 0 :(得分:2)

您可以读取19字节的小块文件,并使用'A7fff'进行打包。不要使用结构指针('p''P'),因为它们需要超过19个字节来编码您的信息。 您还可以使用'A6xfff'忽略第7个字节,并获得一个包含6个字符的字符串。

这是一个示例,类似于IO.read的文档:

data = [["ABCDEF\t", 3.4, 5.6, 9.1], 
        ["FEDCBA\t", 2.5, 8.9, 3.1]]
binary_file = 'data.bin'
chunk_size = 19
pattern = 'A7fff'

File.open(binary_file, 'wb') do |o|
  data.each do |row|
    o.write row.pack(pattern)
  end
end

raise "Something went wrong. Please check data, pattern and chunk_size." unless File.size(binary_file) == data.length * chunk_size

File.open(binary_file, 'rb') do |f|
  while record = f.read(chunk_size)
    puts '%s %g %g %g' % record.unpack(pattern)
  end
end
# =>
#    ABCDEF   3.4 5.6 9.1
#    FEDCBA   2.5 8.9 3.1

如果文件很大,则可以使用19的倍数来加快该过程。

答案 1 :(得分:0)

当处理重复且已知大小固定的混合格式时,通常更容易先分割字符串,

简单的例子是:

binary.scan(/.{LENGTH_OF_DATA}/).map { |item| item.unpack(FORMAT) }

考虑上面的示例,请考虑字符串的长度(包括制表符)(以字节为单位),再加上3个浮点数的大小。如果您的字符串实际上是'ABCDEF\t',则应使用19的大小(字符串为7,三个浮点数为12)。

您的最终产品将如下所示:

str.scan(/.{19}/).map { |item| item.unpack('P7fff') }

每个示例:

irb(main):001:0> ary = ["ABCDEF\t", 3.4, 5.6, 9.1, "FEDCBA\t", 2.5, 8.9, 3.1]
=> ["ABCDEF\t", 3.4, 5.6, 9.1, "FEDCBA\t", 2.5, 8.9, 3.1]

irb(main):002:0> s = ary.pack('pfffpfff')
=> "\xE8Pd\xE4eU\x00\x00\x9A\x99Y@33\xB3@\x9A\x99\x11A\x98Pd\xE4eU\x00\x00\x00\x00 @ff\x0EAffF@"

irb(main):003:0> s.unpack('pfffpfff')
=> ["ABCDEF\t", 3.4000000953674316, 5.599999904632568, 9.100000381469727, "FEDCBA\t", 2.5, 8.899999618530273, 3.0999999046325684]

精度上的微小差异是不可避免的,但不必担心,因为它来自32位浮点数和64位双精度数(内部使用Ruby)的差异,并且精度差异小于对于32位浮点数非常重要。