Как я могу получить доступ к n-му байту двоичного скаляра в Perl?

Спасибо всем заранее.

Я хотел бы получить доступ к n-му байту двоичного скаляра. Например, вы можете получить все данные файла в одной скалярной переменной...

Представьте, что бинарные данные собираются в скаляр...

open(SOURCE, "<", "wl.jpg"); 
my $thisByteData = undef; 
while(<SOURCE>){$thisByteData .= $_;} 
close SOURCE; 

$thisByteData — это необработанные двоичные данные. Когда я использую length($thisByteData), я возвращаю счетчик байтов, поэтому Perl знает, насколько он велик. Мой вопрос: как я могу получить доступ к N-му байту?

Боковое примечание: моя функция получит этот двоичный скаляр, в моей функции я хочу получить доступ к N-му байту. Помощь в сборе этих данных приветствуется, но не то, что я ищу. Каким бы способом другой программист ни собирал двоичные данные, зависит от них, моя задача — получить N-й байт, когда он будет передан мне :)

Еще раз огромное всем спасибо за помощь!


Спасибо @muteW, который помог мне добиться большего, чем когда-либо. Наверное, я неправильно понимаю unpack(...).

print(unpack("N1", $thisByteData));
print(unpack("x N1", $thisByteData));
print(unpack("x0 N1", $thisByteData));

Возвращает следующее:

4292411360
3640647680
4292411360

Я бы предположил, что все эти 3 строки будут иметь доступ к одному и тому же (первому) байту. Использование без «x», а только «x» и «x $ pos» дает неожиданные результаты.

Я тоже пробовал это...

print(unpack("x0 N1", $thisByteData));
print(unpack("x1 N1", $thisByteData));
print(unpack("x2 N1", $thisByteData));

Который возвращает... то же самое, что и последний тест...

4292411360
3640647680
4292411360

Я определенно упускаю что-то о том, как работает распаковка.


Если я сделаю это...

print(oct("0x". unpack("x0 H2", $thisByteData)));
print(oct("0x". unpack("x1 H2", $thisByteData)));
print(oct("0x". unpack("x2 H2", $thisByteData)));

Я получаю то, что ожидал...

255
216
255

Не можете распаковать и дать мне это без использования oct()?


В качестве примечания: я думаю, что получаю дополнение до 2 этих байтовых целых чисел при использовании «x $ pos N1». Я ожидаю, что это первые 3 байта.

255
216
255

Еще раз спасибо за помощь всем.


Особая благодарность @brian d foy и @muteW... Теперь я знаю, как получить доступ к N-му байту моего двоичного скаляра с помощью unpack(...). У меня есть новая проблема, которую нужно решить сейчас, которая не связана с этим вопросом. Еще раз спасибо всем за помощь, ребята!

Это дало мне желаемый результат...

print(unpack("x0 C1", $thisByteData));
print(unpack("x1 C1", $thisByteData));
print(unpack("x2 C1", $thisByteData));

unpack(...) имеет массу опций, поэтому я рекомендую всем, кто читает это, прочитать документацию по упаковке/распаковке, чтобы получить результат байтовых данных по своему выбору. Я также не пытался использовать параметры Tie, упомянутые @brian, я хотел, чтобы код был как можно более простым.


person Community    schedule 16.07.2009    source источник
comment
Пожалуйста, отредактируйте этот комментарий в исходном вопросе.   -  person Chris Lutz    schedule 17.07.2009
comment
При обработке двоичных данных вы всегда должны использовать binmode в дескрипторе файла сразу после его открытия (т.е. перед началом чтения/записи).   -  person Michael Carman    schedule 17.07.2009
comment
Что ты пытаешься сделать? Если вы хотите извлечь информацию из файла изображения, вероятно, уже есть модуль, который сделает это за вас.   -  person brian d foy    schedule 17.07.2009
comment
Это плохой способ прочитать все данные в скаляр. Если вы не собираетесь использовать все двоичные данные, зачем читать весь файл?   -  person Brad Gilbert    schedule 17.07.2009
comment
Я не думаю, что вам действительно нужно показывать нам, как вы читаете файл, поскольку это не имеет отношения к обсуждению.   -  person Brad Gilbert    schedule 17.07.2009
comment
@Брэд, спасибо за ответы. :) Я никогда не говорил, что не собираюсь использовать все данные, на самом деле я это планирую. Если вы прочитаете ответ и ветку @muteW ниже ... вы увидите, где я сейчас.   -  person rakhavan    schedule 17.07.2009


Ответы (5)


Поскольку у вас уже есть содержимое файла в $thisByteData, вы можете использовать pack/unpack для доступа к n-му байту.

sub getNthByte {
  my ($pos) = @_;
  return unpack("x$pos b1", $thisByteData);
}

#x$pos - treats $pos bytes as null bytes(effectively skipping over them) 
#b1    - returns the next byte as a bit string

Прочтите документацию пакета, чтобы получить представление о параметрах, которые можно использовать в шаблоне для получения различных возвращаемых значений.

РЕДАКТИРОВАТЬ. Ваш комментарий ниже показывает, что вам не хватает старшего полубайта ('f') первого байта. Я не уверен, почему это происходит, но вот альтернативный метод, который работает, а пока я еще посмотрю на поведение распаковки.

sub getNthByte {
  my ($pos) = @_;
  return unpack("x[$pos]H2", $binData);
}

(my $hex = unpack("H*", $binData)) =~ s/(..)/$1 /g;
#To convert the entire data in one go

Используя это, вывод для первых четырех байтов: 0xff 0xd8 0xff 0xe0, что соответствует документация.

person aks    schedule 17.07.2009
comment
Это завело меня очень далеко! Моя единственная проблема сейчас заключается в том, что в начале ввода я пропускаю байты. - person rakhavan; 17.07.2009
comment
Что вы получаете в позиции 0/1/2? Сколько байтов он пропускает, и, если возможно, вы могли бы опубликовать здесь шестнадцатеричные значения пропущенных байтов. - person aks; 17.07.2009
comment
Это забавно! Мне не хватает первых 3 байт. распаковать(x0 h1, $thisByteData) = f распаковать(x1 h1, $thisByteData) = 8 распаковать(x2 h1, $thisByteData) = f распаковать(x0 H1, $thisByteData) = f распаковать(x1 H1, $thisByteData) = d unpack(x2 H1, $thisByteData) = f Это тот результат, который вы просили? - person rakhavan; 17.07.2009
comment
Нет, ты не такой. Согласно этому onicos.com/staff/iz/formats/jpeg.html первые три байта 0xff 0xd8 0xff в формате с прямым порядком байтов. Проверьте свой вывод - вы получаете один и тот же вывод, разделенный на кусочки (из-за использования h и H). - person aks; 17.07.2009
comment
Мне также кажется, что отсутствуют первые 3 байта, когда я передаю строку этой функции... как вы думаете, это случайно? - person rakhavan; 17.07.2009
comment
Было бы намного полезнее, если бы вы могли подробно описать текущий код/вывод в OP, чтобы больше людей могли его посмотреть. - person aks; 17.07.2009

Если у вас есть данные в виде строки и вы хотите перейти к определенному байту, используйте substr, если вы начинаете с обработки строки как байтов.

Тем не менее, вы можете прочитать его прямо из файла без всей этой ерунды со строками, которой люди забили вам голову. :) Откройте файл с sysopen и нужными параметрами, используйте seek, чтобы поставить себя там, где вы хотите, и прочитать то, что вам нужно, с помощью sysread.

Вы пропускаете все обходные пути для вещей, которые open и readline пытаются сделать для вас. Если вы просто собираетесь отключить все их функции, даже не используйте их.

person brian d foy    schedule 16.07.2009
comment
@brain, так как я пытался и не могу получить доступ к скаляру, как к массиву ... Я собирался использовать substr, затем сказал себе, эй, это не строка. Другая половина монеты здесь в том, что мне нужно обработать эти данные как можно быстрее... является ли substr самым быстрым способом добраться до N-го символа? - person rakhavan; 17.07.2009
comment
@brian, а это ЕДИНСТВЕННЫЙ способ получить доступ к N-му байту скаляра? - person rakhavan; 17.07.2009
comment
Если бы это был я, я бы сделал то, что я рекомендовал. Что касается всего, держу пари, я могу придумать как минимум пять совершенно разных способов сделать это. Я бы по-прежнему перемещался по файлу только с поиском, как я уже сказал. - person brian d foy; 17.07.2009
comment
Вы можете получить доступ к скаляру как к массиву через механизм Tie. Я показал пример этого в Mastering Perl, когда говорил о длинной строке ДНК. - person brian d foy; 18.07.2009
comment
Посмотрите на мой обновленный ответ. Чтобы получить вывод unsigned char вместо Hex, просто замените «H» на «C». - person aks; 18.07.2009

Я думаю, что правильный ответ включает в себя упаковку/распаковку, но это также может работать:

use bytes;
while( $bytestring =~ /(.)/g ){
   my $byte = $1;
   ...
}

«использовать байты» гарантирует, что вы никогда не увидите символы, но если у вас есть строка символов и вы обрабатываете ее как байты, вы делаете что-то не так. Внутренняя кодировка символов Perl не определена, поэтому данные, которые вы видите в строке «использовать байты», почти бессмысленны.

person jrockway    schedule 16.07.2009

Встроенная переменная Perl $/ (или $INPUT_RECORD_SEPARATOR, если вы use делаете English) управляет идеей Perl о "линии". По умолчанию установлено значение "\n", поэтому строки разделяются символами новой строки (да), но вы можете изменить это на любую другую строку. Или измените его на ссылку на число:

$/ = \1;
while(<FILE>) {
  # read file
}

Установка его на ссылку на число сообщит Perl, что "строка" - это количество байтов.

Теперь, что именно вы пытаетесь сделать? Вероятно, существует ряд модулей, которые будут делать то, что вы пытаетесь сделать, и, возможно, более эффективно. Если вы просто пытаетесь научиться это делать, продолжайте, но если у вас есть конкретная задача, подумайте о том, чтобы не изобретать велосипед (если вы этого не хотите).

РЕДАКТИРОВАТЬ: Спасибо jrockway в комментариях...

Если у вас есть данные в формате Unicode, может быть прочитан не один байт, а один символ, но если это произойдет, вы сможете use bytes; отключить автоматический перевод байтов в символы.

Теперь вы говорите, что хотите прочитать все данные сразу, а затем передать их функции. Давай сделаем это:

my $data;
{
  local $/;
  $data = <FILE>;
}

Или это:

my $data = join("", <FILE>);

Или некоторые предложат модуль File::Slurp, но я думаю, что это немного перебор. Однако давайте запишем весь файл в массив байтов:

use bytes;

...

my @data = split(//, join("", <FILE>));

И затем у нас есть массив байтов, который мы можем передать функции. Нравится?

person Chris Lutz    schedule 16.07.2009
comment
Верно ли это предположение, когда слои perlio декодируют байты в символы? - person jrockway; 17.07.2009
comment
Я бы хотел, как я, проглотить файл и передать все эти байтовые данные другой функции. Внутри этой функции я хочу получить доступ к каждому байту один за другим. - person rakhavan; 17.07.2009
comment
@jrockway - Вероятно, нет, но вы можете use bytes; отключить это. Хорошая точка зрения. - person Chris Lutz; 17.07.2009
comment
Вместо того, чтобы вставлять строку EDIT:, как насчет исправления всего ответа? Вам не нужно показывать каждую версию вашего ответа в готовой версии. И не забывайте про binmode. :) - person brian d foy; 17.07.2009
comment
@Chris Lutz: не используйте для этого байты, используйте binmode. На самом деле нет хороших вариантов использования байтов. - person ysth; 17.07.2009
comment
На самом деле, если вы пытаетесь получить массив байтов, use bytes; $/ = \1; @array = <$file_handle>; будет работать лучше, чем split m'', ... - person Brad Gilbert; 17.07.2009
comment
@Brad Gilbert: не используйте для этого байты, используйте binmode. На самом деле нет хороших вариантов использования байтов. - person ysth; 29.07.2009

Не зная гораздо больше о том, что вы пытаетесь сделать со своими данными, что-то вроде этого будет перебирать байты в файле:

open(SOURCE, "wl.jpg");
my $byte;
while(read SOURCE, $byte, 1) {
    # Do something with the contents of $byte
}
close SOURCE;

Будьте осторожны с конкатенацией, используемой в вашем примере; вы можете получить преобразование новой строки, что определенно не то, что вы хотите, когда читаете двоичные файлы. (Также неэффективно постоянно расширять скаляр во время его чтения.) Это идиоматический способ превратить весь файл в скаляр Perl:

open(SOURCE, "<", "wl.jpg");
local $/ = undef;
my $big_binary_data = <SOURCE>;
close SOURCE;
person Commodore Jaeger    schedule 16.07.2009
comment
Отличное замечание по поводу дополнительных пробелов, мой код - это просто пример получения всех байтов в одной переменной, поэтому я могу передать их все сразу... Теперь, если бы я мог использовать while(read... как вы выше, и создать массив, полный моих байтов, но мне было интересно, есть ли в Perl способ доступа к байтовым данным, таким как массив. - person rakhavan; 17.07.2009