【问题标题】:How to extract a pattern from string containing binary data如何从包含二进制数据的字符串中提取模式
【发布时间】:2019-06-18 17:59:53
【问题描述】:

我有这个数组来自之前的a=array.unpack("C*") 命令。

a = [9, 32, 50, 53, 56, 53, 57, 9, 73, 78, 70, 79, 9, 73, 78, 70, 79, 53, 9, 
     32, 55, 52, 32, 50, 51, 32, 48, 51, 32, 57, 50, 32, 48, 48, 32, 48, 48, 32, 
     48, 48, 32, 69, 67, 32, 48, 50, 32, 49, 48, 32, 48, 48, 32, 69, 50, 32, 48, 
     48, 32, 55, 55, 9, 0, 0, 0, 0, 1, 12, 1, 0, 0, 0, 57, 254, 70, 6, 1, 6, 0, 3, 
     0, 3, 198, 0, 2, 198, 31, 147, 23, 0, 226, 7, 12, 17, 18, 56, 55, 3, 101, 1, 
     1, 0, 134, 7, 145, 5, 148, 37, 150, 133, 241, 135, 5, 22, 109, 145, 53, 38, 
     171, 4, 3, 2, 6, 192, 173, 22, 160, 20, 48, 18, 6, 9, 42, 134, 58, 0, 137, 97, 
     58, 1, 0, 164, 5, 48, 3, 129, 1, 7, 225, 16, 2, 1, 1, 4, 11, 9, 1, 10, 10, 6, 
     2, 19, 105, 145, 103, 116, 226, 35, 48, 3, 194, 1, 242, 48, 3, 194, 1, 241, 48, 
     3, 194, 1, 246, 48, 3, 194, 1, 245, 48, 3, 194, 1, 244, 48, 3, 194, 1, 243, 48, 
     3, 194, 1, 247, 177, 13, 10, 1, 1, 4, 8, 10, 6, 2, 19, 105, 145, 103, 116, 0, 0, 
     42, 3, 0, 0, 48, 48, 48, 48, 48, 48, 48, 50, 9, 82, 101, 99, 101, 105, 118, 101, 
     9, 50, 51, 9, 77, 111, 110, 32, 32]

当我转换为 chr 时,它看起来像这样:

 irb(main):4392:0> a.map(&:chr).join
 => "\t 25859\tINFO\tINFO5\t 74 23 03 92 00 00 00 EC 02 10 00 E2 00 77\t\x00\x00\x00\x00
 \x01\f\x01\x00\x00\x009\xFEF\x06\x01\x06\x00\x03\x00\x03\xC6\x00\x02\xC6\x1F\x93\x17\x00
 \xE2\a\f\x11\x1287\x03e\x01\x01\x00\x86\a\x91\x05\x94%\x96\x85\xF1\x87\x05\x16m\x915&\xAB
 \x04\x03\x02\x06\xC0\xAD\x16\xA0\x140\x12\x06\t*\x86:\x00\x89a:\x01\x00\xA4\x050\x03\x81
 \x01\a\xE1\x10\x02\x01\x01\x04\v\t\x01\n\n\x06\x02\x13i\x91gt\xE2#0\x03\xC2\x01\xF20\x03
 \xC2\x01\xF10\x03\xC2\x01\xF60\x03\xC2\x01\xF50\x03\xC2\x01\xF40\x03\xC2\x01\xF30\x03\xC2
 \x01\xF7\xB1\r\n\x01\x01\x04\b\n\x06\x02\x13i\x91gt\x00\x00*\x03\x00\x000000..."

我想提取INFO5\t\t..., 之间的十六进制值,所以输出是

 "74 23 03 92 00 00 00 EC 02 10 00 E2 00 77"     

我正在像下面那样做,但只删除了第一个不需要的部分并留下\n\n\x06...000

我该如何解决这个问题?

irb(main)>: a.map(&:chr).join.gsub(/(\t .*\t )|(\t.*)/,"")
=> "74 23 03 92 00 00 00 EC 02 10 00 E2 00 77\n\n\x06\x02\x13i\x91gt\xE2#0
\x03\xC2\x01\xF20\x03\xC2\x01\xF10\x03\xC2\x01\xF60\x03\xC2\x01\xF50\x03\xC2
\x01\xF40\x03\xC2\x01\xF30\x03\xC2\x01\xF7\xB1\r\n\x01\x01\x04\b\n\x06\x02\
x13i\x91gt\x00\x00*\x03\x00\x0000000002"

提前感谢您的帮助。

UDPATE

下面附上示例二进制文件。

input.dat

【问题讨论】:

  • 我建议将标题编辑为“如何从包含二进制数据的字符串中提取模式”(无需提及“ruby”,因为它已经是一个标签)
  • 完成!非常感谢您的建议

标签: arrays ruby ascii extraction


【解决方案1】:
  1. 我假设您不需要非 ascii 字节,所以在第一步中,我使用take_while 将它们修剪为第一个空字节
  2. 然后我使用map(&:chr).join将整数转换为字符串
  3. 最后我match他们使用一个正则表达式/INFO5\t ?([^\t]*)\t/假设有趣的部分在INFO5\t和下一个\t之间

--

a=array.unpack("C*")
a.take_while{|e| e > 0}.map(&:chr).join.match(/INFO5\t ?([^\t]*)\t/)[1]
# => "74 23 03 92 00 00 00 EC 02 10 00 E2 00 77"

【讨论】:

  • 您好,先生,感谢您的帮助。仅当我先执行 a=hexstring.unpack("C*") 然后应用您的代码时,它才有效。如果我尝试将代码直接应用于十六进制字符串,则会收到错误ArgumentError: invalid byte sequence in UTF-8 这是这样做hexstring.match(/INFO5\t ?([^\t]*)\t/)[1]
  • 请看编辑。我使用了a你在问题中的数组
  • 是的,我的意思是hextring 看起来与a.map(&:chr).join 相同,但如果我直接应用于十六进制字符串则不起作用。我试过这样做是为了知道我是否在做额外的步骤,但似乎我需要先做a=hextring.unpack("C*"),然后应用你的代码才能工作。
【解决方案2】:

我假设你的意思是 a=str.unpack("C*") - 你可以 unpack 一个字符串但不是一个数组。

要获得您想要的结果,您根本不需要使用unpack1 - 只需执行一个正则表达式:

str.match(/INFO5\t(.*?)\t/).to_a[1]
# => " 74 23 03 92 00 00 00 EC 02 10 00 E2 00 77"

请注意,结果中有一个前导空格,但您可以根据需要调整正则表达式;我不会去猜测这种格式的规范。

提示:

  • 需要.*? 中的? 才能使* 不贪婪。
  • to_a 可避免raisematch 找不到任何结果时出错。

编辑

您关于“UTF-8 中的无效字节序列”的评论表明您的数据可能是 ASCII-8BIT(即它与 UTF-8 不兼容),但它存储在编码属性为“UTF-8”的字符串中.如果您解释一下您是如何获得该字符串的,将会有所帮助,因为字符串的编码似乎是错误的。

解决方案 1(这是理想的):

以 ASCII-8BIT 格式读入文件:

str = File.read("input.dat", encoding: 'ASCII-8BIT')

解决方案 2(一种解决方法,如果您无法控制输入编码):

# NOTE: this changes the encoding on `str`
str.force_encoding("ASCII-8BIT")

完成此操作后,.match 应该可以工作了。

进一步说明

map(&:chr).join 起作用的原因是因为.chr 将产生US-ASCIIASCII-8BIT 字符串(后者发生在127 以上的字节),从不产生UTF-8

当你 join 那些字符串时,如果任何字节大于 127,你的结果就是 ASCII-8BIT。所以这实际上与调用 force_encoding("ASCII-8BIT") 相同,除了 map/join 不会修改原始字符串的编码就像force_encoding 一样。


1unpack 是不必要的,因为a.map(&:chr).joinarr.pack('C*') 相同,它为您提供原始str。即使您不得不为其他目的unpack 字符串,我建议使用原始字符串而不是重新pack 数组。也许您可以将其封装到数据结构中,例如:
i_data = InfoData.new(str)
i_data.bytes  # array of bytes
i_data.hex_string  # "74 23 03 ..."

请注意,上面的代码不会按原样运行 - 您需要自己编写 InfoData 类。

【讨论】:

  • 您好,Kelvin,感谢您的回答。当我尝试 str.match(/INFO5\t(.*?)\t/).to_a[1] 时,我从 (irb):4463:in match' from (irb):4463:in block in irb_binding' 得到 ArgumentError: invalid byte sequence in UTF-8 from (irb):4463:in match' 从 (irb):4454:in foreach' from (irb):4454
  • 当我尝试InfoData.new(str) 我得到这个错误i_data = InfoData.new(str) NameError: uninitialized constant InfoData
  • 似乎即使a.map(&:chr).join = arr.pack('C*') = 原始字符串,如果我先不做a=string.unpack("C*"),你的代码就不起作用,我收到invalid byte sequence in UTF-8 from
  • 嗨,Kelvin。在我的真实文件中,我使用像这样的字节序列作为行分隔符读取它:File.foreach("input.dat", sep="\x10\x12", encoding: 'ASCII-8BIT') do |line| next unless line =~ /INFO5/ p line.match(/INFO5\t ?([^\t]*)\t/)[1] end 所以现在有了关于 ASCII-8BIT 和 UTF-8 的解释和解决方案 1 和 2,我将 encoding: ASCII-8BIT 添加到 foreach 就像这样File.foreach("input.dat", sep="\x10\x12", encoding: 'ASCII-8BIT') 现在它工作得很好。非常感谢您的帮助。
  • 还有一个问题,要读取这种我用字节序列作为分隔符的文件,最好使用File.foreach(),还是有更好的方法?
【解决方案3】:

这里有两种方法(下面的a 是问题中给出的缩写)。

a = [9, 32, 50, 53, 56, 53, 57, 9, 73, 78, 70, 79, 9, 73, 78, 70, 79, 53, 9, 
     32, 55, 52, 32, 50, 51, 32, 48, 51, 32, 57, 50, 32, 48, 48, 32, 48, 48,
     32, 48, 48, 32, 69, 67, 32, 48, 50, 32, 49, 48, 32, 48, 48, 32, 69, 50,
     32, 48, 48, 32, 55, 55, 9, 0, 0]

从解压后的字符串中提取a

str = a.pack("C*")
  #=> "\t 25859\tINFO\tINFO5\t 74 23 03 92 00 00 00 EC 02 10 00 E2 00 77\t\x00\x00"

str[/(?<=INFO5\t).+?(?=\t)/].strip
  #=> "74 23 03 92 00 00 00 EC 02 10 00 E2 00 77" 

str是已经转换成aa = str.unpack("C*))的字符串,所以不需要计算。

(?&lt;=INFO5\t )(?=\t) 分别是正向的后视正向的前瞻。它们必须匹配,但不是返回的匹配的一部分。 .+? 中的(“非贪婪”)问号确保匹配在遇到第一个制表符之前立即终止。相比之下,

"abc\td\tef"[/(?<=a).+(?=\t)/]
  #=> "bc\td" 

a中提取并转换为字符串

pfix = "INFO5\t".unpack("C*")
  #=> [73, 78, 70, 79, 53, 9]
pfix_size = pfix.size
  #=> 6 
sfix = [prefix.last]
  #=> [9]
sfix_size = sfix.size
start = idx_start(a, pfix) + pfix_size
  #=> 19
a[start..idx_start(a[start..-1], sfix) + start - 1].pack("C*").strip
  #=> "74 23 03 92 00 00 00 EC 02 10 00 E2 00 77"

def idx_start(a, arr)
  arr_size = arr.size
  a.each_index.find { |i| a[i, arr_size] == arr }
end

【讨论】:

  • 嗨,卡里。感谢您的回答。当我尝试您的解决方案时,我得到ArgumentError: invalid byte sequence in UTF-8
  • 嗨,卡里。请看我的更新。我附上了一个包含十六进制字符串的示例文件。对我来说,错误只发生在二进制文件上,但如果我将它们应用于文本字符串,你的解决方案就可以工作。
  • 非常感谢您更新的解决方案。你给了我更多的想法来应用到我的代码中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-15
  • 2022-01-21
  • 1970-01-01
相关资源
最近更新 更多