Как извлечь шаблон из строки, содержащей двоичные данные - PullRequest
0 голосов
/ 25 января 2019

У меня есть этот массив, полученный из предыдущей команды 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

Ответы [ 3 ]

0 голосов
/ 25 января 2019

Полагаю, вы имеете в виду 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"

Обратите внимание, что в результате есть пробел, но вы можете настроить регулярное выражение в соответствии со своими потребностями;Я не буду пытаться угадать спецификацию этого формата.

Советы:

  • ? в .*? необходим, чтобы * нежадный.
  • to_a позволяет избежать raise ошибки в случае, если match ничего не находит.

РЕДАКТИРОВАТЬ

Ваш комментарий относительно «недопустимой последовательности байтов в 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 эти строки, ваш результат в ASCII-8BIT, если какой-либо байт был выше 127. Так что этофактически аналогично вызову force_encoding("ASCII-8BIT"), за исключением того, что map / 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 самостоятельно.

0 голосов
/ 25 января 2019

Вот два подхода (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)соответственно положительный взгляд за и положительный взгляд .Они должны быть сопоставлены, но не являются частью совпадения, которое возвращается.Знак вопроса («не жадный») в .+? гарантирует, что совпадение прекращается непосредственно перед первой вкладкой.Напротив,

"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
0 голосов
/ 25 января 2019
  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"
...