如何从包含二进制数据的字符串中提取模式
How to extract a pattern from string containing binary data
我有这个数组来自之前的 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
下面附上示例二进制文件。
- 我假设您不需要非 ascii 字节,所以在第一步中我使用
take_while
trim 它们到第一个空字节
- 然后我使用
map(&:chr).join
将整数转换为字符串
- 最后我
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=str.unpack("C*")
- 你可以 unpack
一个字符串而不是一个数组。
要得到你想要的结果,你根本不需要使用unpack
1 - 只需执行正则表达式:
str.match(/INFO5\t(.*?)\t/).to_a[1]
# => " 74 23 03 92 00 00 00 EC 02 10 00 E2 00 77"
请注意,结果中有一个前导 space,但您可以根据需要调整正则表达式;我不会去猜测这种格式的规范。
提示:
- 需要
.*?
中的 ?
才能使 *
非贪婪。
to_a
避免 raise
在 match
找不到任何东西的情况下出现错误。
编辑
您关于 "invalid byte sequence in 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-ASCII
或 ASCII-8BIT
字符串(后者发生在 127 以上的字节),而不是 UTF-8
.
当你 join
这些字符串时,如果任何字节大于 127,你的结果在 ASCII-8BIT
中。所以这实际上与调用 force_encoding("ASCII-8BIT")
相同,除了 [=32] =]/join
不会像 force_encoding
那样修改原始字符串的编码。
1unpack
是不必要的,因为 a.map(&:chr).join
与 arr.pack('C*')
相同,后者为您提供原始的 str
。即使您出于其他目的不得不 unpack
字符串,我也建议使用原始字符串而不是重新 pack
数组。也许你可以把它封装成一个数据结构,例如:
i_data = InfoData.new(str)
i_data.bytes # array of bytes
i_data.hex_string # "74 23 03 ..."
请注意,上面的代码不会按原样运行 - 您需要自己编写 InfoData
class。
这里有两种方法(下面的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
是已经转换为a
的字符串(a = str.unpack("C*)
),所以不需要计算。
(?<=INFO5\t )
和 (?=\t)
分别是 正向后视 和 正向前视 。它们必须匹配但不是返回的匹配项的一部分。 .+?
中的 ("non-greedy") 问号确保匹配在遇到第一个制表符之前立即终止。相比之下,
"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
我有这个数组来自之前的 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
下面附上示例二进制文件。
- 我假设您不需要非 ascii 字节,所以在第一步中我使用
take_while
trim 它们到第一个空字节
- 然后我使用
map(&:chr).join
将整数转换为字符串
- 最后我
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=str.unpack("C*")
- 你可以 unpack
一个字符串而不是一个数组。
要得到你想要的结果,你根本不需要使用unpack
1 - 只需执行正则表达式:
str.match(/INFO5\t(.*?)\t/).to_a[1]
# => " 74 23 03 92 00 00 00 EC 02 10 00 E2 00 77"
请注意,结果中有一个前导 space,但您可以根据需要调整正则表达式;我不会去猜测这种格式的规范。
提示:
- 需要
.*?
中的?
才能使*
非贪婪。 to_a
避免raise
在match
找不到任何东西的情况下出现错误。
编辑
您关于 "invalid byte sequence in 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-ASCII
或 ASCII-8BIT
字符串(后者发生在 127 以上的字节),而不是 UTF-8
.
当你 join
这些字符串时,如果任何字节大于 127,你的结果在 ASCII-8BIT
中。所以这实际上与调用 force_encoding("ASCII-8BIT")
相同,除了 [=32] =]/join
不会像 force_encoding
那样修改原始字符串的编码。
1
unpack
是不必要的,因为 a.map(&:chr).join
与 arr.pack('C*')
相同,后者为您提供原始的 str
。即使您出于其他目的不得不 unpack
字符串,我也建议使用原始字符串而不是重新 pack
数组。也许你可以把它封装成一个数据结构,例如:
i_data = InfoData.new(str)
i_data.bytes # array of bytes
i_data.hex_string # "74 23 03 ..."
请注意,上面的代码不会按原样运行 - 您需要自己编写 InfoData
class。
这里有两种方法(下面的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
是已经转换为a
的字符串(a = str.unpack("C*)
),所以不需要计算。
(?<=INFO5\t )
和 (?=\t)
分别是 正向后视 和 正向前视 。它们必须匹配但不是返回的匹配项的一部分。 .+?
中的 ("non-greedy") 问号确保匹配在遇到第一个制表符之前立即终止。相比之下,
"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