【问题标题】:Ruby - Unpack array with mixed typesRuby - 解包混合类型的数组
【发布时间】:2019-06-12 05:05:42
【问题描述】:

我正在尝试使用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>'

【问题讨论】:

  • P7 是问题所在,请尝试将p 更改为小写(第 7 位)。打包/拆包时存在一些差异。读取文件时,您使用P7,因为它不是空终止符,但再次打包时,它是。我只是通过使用P7fffP7fff 打包并使用pfffpfff 解包来正确使用示例。
  • 我得到同样的错误
  • 您的示例使用了一个数组,其中每个项目已经分开,因此您将使用较低的p。读取文件时,它将是一串字节,没有被分隔成数组项,因此您必须使用大写变体P7指定固定长度。
  • 好的。我今晚回家后会尝试访问该文件。
  • @ForeverZer0:pP 都是问题。

标签: ruby binaryfiles binary-data pack unpack


【解决方案1】:

您可以读取 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 的倍数来加快处理速度。

【讨论】:

  • A7fff 成功了。即使不写入文件,我也可以使用 s.scan(/.{19}/).map{ |item| item.unpack('A7fff') } 解压它
  • @Rojj:当然,如果您已经拥有数据,则无需编写任何内容。只是为了有一个通用的二进制数据来调试和测试。 scan 也可以,但它需要将整个文件保存在内存中,如果您使用大文件,这可能不适合。
  • @Rojj:如果你不关心字符串的最后一个字符,你也可以使用'A6xfff',如["ABCDEF\t", 3.4, 5.6, 9.1].pack('A7fff').unpack('A6xfff')
  • 哦,太好了!谢谢
【解决方案2】:

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

简单的例子是:

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

考虑到您上面的示例,取包括制表符在内的字符串长度(以字节为单位),再加上 3 个浮点数的大小。如果您的字符串字面意思是'ABCDEF\t',您将使用 19 的大小(字符串为 7,3 个浮点数为 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 位浮点数很重要。

【讨论】:

  • 漂亮,但我有问题。我使用File.binread'. This gives me a String` 从文件中读取了字符串,而String 没有each_slice 方法。我试图将其转换为 byteschars,但这给了我数组,并且 unpack 不适用于数组。 each_slice 是否适用于 Ruby 2.0.0 的字符串?
  • 抱歉,我更正了使用String#scan 而不是each_slice 的答案。您也可以使用str.chars.each_slice,但scan 是一种更清洁的方法IMO。
  • 我收到no associated pointer。我添加了一个示例,所以我们看的完全一样。
  • p / P 在这里不是正确的指令。使用aa6x(x 忽略制表符)作为P7 表示七个指向Ruby 字符串的指针。
  • 小写的p 表示,但对于大写的P,它表示结构的大小。使用['stack', 'overflow'].pack('pp').unpack('P5P8') 轻松演示。它不会尝试解压 13 个指针。
猜你喜欢
  • 2014-04-28
  • 2021-12-15
  • 1970-01-01
  • 2014-09-10
  • 1970-01-01
  • 2022-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多