【问题标题】:Extract hex strings from binary file in Ruby从Ruby中的二进制文件中提取十六进制字符串
【发布时间】:2019-01-05 23:35:42
【问题描述】:

对于二进制文件,我想提取绿色(日期和小时)和蓝色的十六进制字符串。蓝色的十六进制字符串在字节 09 和 00 之间。

我已经能够使用正则表达式提取日期和小时以及部分蓝色的十六进制字符串。为此,我将字节 09 (\x09)

设置为“行分隔符”

我可能遇到的问题可以用正则表达式来解决,以获取 09 和 00 之间的字符串,但目前使用我的正则表达式 (^20.*) 我变得不受欢迎 和非ASCII字节。有人可以帮我获取 09 到 00 之间的字节数。

我当前的代码:

BEGIN{  $/="\x09".force_encoding("BINARY")   }

IO.foreach("file.dat"){ |l|

    line = l.unpack('H*')[0]
    next unless line =~ /(.{8}2d.{4}2d.{4})20(.{4}3a.{4}3a.{4})|(^20.*)/

        if ( $1 != nil and $2 != nil )
            date = $1
            hour = $2
            p date.gsub(/../) { |b| b.hex.chr }
            p hour.gsub(/../) { |b| b.hex.chr } 
        end

        if $3 != nil            
            p $3.gsub(/20/,"").gsub(/../) { |b| b.hex.chr }         
        end 
}

电流输出

"2017-10-19"
"15:43:27"
"83492624790981030E100000\x00\x18\v\x16\x84\x13\x05$B#q\x000\x03\x81\x01\n\x00\x00v\x00\x0000000003\t"
"2017-12-05"
"09:32:15"
"001104059419632801001B237100300381010A0000\x00\x00\x00\x00\x02\xD0\x00\x00\x00\b\xFEF\xCC\x00\x06\xE7\f\x13\x0F+\e\xB5\xE1/\x00\xB5\x83I&$y\t"
=> nil

预期输出

"2017-10-19"
"15:43:27"
"83492624790981030E100000"
"2017-12-05"
"09:32:15"
"001104059419632801001B237100300381010A0000"
=> nil

文件如下所示:

附加示例文件: file.dat

【问题讨论】:

  • 尝试类似:s.each_byte.map { |b| b.to_s(16) }.join
  • 嗨 Aaditya,我已经像这样测试过,但不起作用,打印错误 3 美元 p $3.each_byte.map { |b| b.to_s(16) }.join

标签: ruby binary-data data-extraction


【解决方案1】:

为了获取以20 开头并以00 结尾的字节,您需要像这样更改正则表达式:

next unless line =~ /(.{8}2d.{4}2d.{4})20(.{4}3a.{4}3a.{4})|^20(.*?0?)0{2}/

基本上我只将正则表达式的最后一部分从(^20.*) 更改为^20(.*?0?)0{2}。 解释如下:

  • 从 20 开始 - ^20
  • 尽可能少匹配 - .*?
  • 直到你得到两个连续的 0 0{2}
  • .*? 之后的0? 处理您拥有X0 00 的情况

另外,我没有将20 包含在捕获的组中,因为无论如何您稍后都会在代码中将其删除,因此您可以在

中删除.gsub(/20/, '')
p $3.gsub(/20/,"").gsub(/../) { |b| b.hex.chr }         

【讨论】:

  • 优秀的 Viktor,我知道修改第二个正则表达式是修复它的方法,但我不知道如何构建正确的正则表达式。非常感谢您的解释。
【解决方案2】:
require 'date'

IN_FNAME = "file.dat"
OUT_FNAME = "out_file.dat"
END_OF_LINE = "\x09"

str_out = ''
File.foreach(IN_FNAME, sep=END_OF_LINE) do |line|
  dt_str = line[3..21]
  if (DateTime.strptime(dt_str, '%Y-%m-%d %H:%M:%S') rescue nil)
    puts dt_str.split(' ')
    next
  end
  arr = line.unpack("C*")
  next unless arr.first == 32
  a = arr.map(&:chr).select { |c| c.match? /\d/ }
  puts a.join
  str_out << a.map(&:ord).pack("C*")    
end
2017-10-19
15:43:27
83492624790981030100000000000003
2017-12-05
09:32:15
0011040594196328010012371003003810100000

最后一步是写入二进制文件OUT_FNAME

File.binwrite(OUT_FNAME, str_out)
  #=> 72

如图所示,写入了 72 个字节。这是使用十六进制编辑器查看时该文件的屏幕截图。

这可以与问题中显示的屏幕截图进行比较。

我们可以读取该文件以确认它是否正确写入。

File.binread(OUT_FNAME)
  #=> "834926247909810301000000000000030011040594196328010012371003003810100000"

请参阅 DateTime::strptimeString#unpack

请注意,打印日期和时间都必须有效。例如,"0000-19-39 29:00:00" 将被简单的正则表达式接受,将被跳过。

【讨论】:

  • 嗨,卡里。感谢您的解决方案。我的最后一步是将提取的十六进制字符串写入二进制文件。如何在您的命令 arr.map(&amp;:chr).select.... 中包含类似 byte.to_i(16). pack('C*') 的内容?
  • 我能够获得此代码,但我不确定是否可以更短 str=arr.map(&amp;:chr).select { |c| c.match /\d/}.each_slice(2).map{|z| z.join}.map{|b| b.to_i(16)}.pack("C*") 和之后的 IO.binwrite("testfile", str)
  • Ger,我对二进制文件没有太多经验,所以如果对pack/unpack 和读/写二进制文件有更深入了解的读者会回答你的问题会更好,最好通过给出解决您完整问题的答案,而不是仅回复您上面的评论。也许@Stefan 会很乐意这样做。
  • 感谢 Cary 的帮助和诚实。我将我的原始代码与您的解决方案混合在一起,因为我以二进制形式阅读,而我认为您以一种更直接、更快速的方式阅读。
  • 嗨,Cary,感谢您继续帮助我。你的解决方案看起来非常紧凑,唯一的事情是我需要输出文件中反映的值是这些:83 49 26 24 79 09 81 03 01 00 00 00 00 00 00 03 00 11 04 05 94 19 63 28 01 00 12 37 10 03 00 38 10 10 00 00 所以,对于从输入文件中捕获的每个数组“a”,我需要这样做str_out &lt;&lt; a.join.scan(/../).map { |b| b.to_i(16) }.pack('C*') &lt;&lt; END_OF_LINE 似乎工作,但我不确定我是否在这部分做额外的步骤或者对你没问题。再次感谢。
猜你喜欢
  • 1970-01-01
  • 2018-09-11
  • 1970-01-01
  • 1970-01-01
  • 2017-03-22
  • 2016-02-26
  • 2021-10-27
  • 2017-07-21
  • 2022-01-25
相关资源
最近更新 更多