Как мне получить доступ к n-му байту двоичного скаляра в Perl? - PullRequest
5 голосов
/ 17 июля 2009

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

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

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

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

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

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

Еще раз большое спасибо за помощь всем!


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

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

Ответы [ 5 ]

8 голосов
/ 17 июля 2009

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

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

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

3 голосов
/ 17 июля 2009

Поскольку у вас уже есть содержимое файла в $ 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, что соответствует документации .

3 голосов
/ 17 июля 2009

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

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

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

2 голосов
/ 17 июля 2009

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

$/ = \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>));

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

1 голос
/ 17 июля 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;
...