Как использовать функцию perl pack для изменения порядка полей

Я пытаюсь изменить порядок полей при построении строки с помощью pack, но не могу заставить pack делать то, что я хочу. Например, я хочу заполнить строку abc по смещению 12, defg по смещению 8 и hi по смещению 3 (и любым другим, предположительно пробелом или \0, по смещениям 0-2 и 5-7).

perl -e '
   use strict; use warnings;
   my $str = "...hi...defgabc";
   my $fmt = q{@12 a3 @8 a4 @3 a2};

   my @a = unpack $fmt, $str;
   print "<$_>\n" for @a;
   print "\n";

   print unpack("H*", pack($fmt, @a)), "\n";
'

Это отлично работает для полей unpacking в любом порядке из строки. Но для packing он \0-заполняется и усекается, как задокументировано. Есть ли способ остановить его от \0-заполнения и усечения без изменения порядка шаблона pack для создания полей слева направо?

Этот вопрос возникает при чтении спецификации поля из внешнего источника. Конечно, шаблон pack можно организовать в порядке слева направо, а результирующий список можно переупорядочить в соответствии со спецификацией внешнего поля. Но было бы удобно динамически перемещать «курсор» pack без заполнения промежуточных позиций или усечения.

В приведенном выше коде я был бы счастлив, если бы возвращаемое значение pack(...) было таким же, как $str с любым байтом для . (например, пустым или \0).


person jrw32982    schedule 28.06.2016    source источник
comment
Я не думаю, что pack можно заставить просто соблюдать то, что находится в строке в этих других позициях, если вы это имеете в виду. Это объединяет. Это работа substr (но с одним в то время).   -  person zdim    schedule 29.06.2016
comment
Итак, кем вы хотите быть в тех других местах, до 3 и между hi и 8? Оригинальные части $str? Что тогда происходит с теми диапазонами, которые были распакованы — они тоже остаются такими, как были (то есть дублируются в выходной строке)?   -  person zdim    schedule 29.06.2016
comment
@zdim На данный момент меня не волнуют другие позиции. Вопрос общий о том, как изменить порядок позиций полей в аргументах pack для шаблона пакета. Конечно, было бы здорово иметь возможность очищать или обнулять их по выбору.   -  person jrw32982    schedule 29.06.2016
comment
OK. Но надо выбирать, что с ними делать -- или, что мы пишем? Если это повторный заказ, это может означать, что то, что было в 3-5, теперь в 12-14 (и наоборот), и это нормально. Или, возможно, вы хотите вместо этого «пробел» (пробелы). Вы берете 16-символьную строку для переупорядочения или выбираете элементы из строки a и записываете 16-символьную длинную строку, а остальные равны .... нулю? Пробелы? НУЛЬ (вздох)? Есть хорошие решения, но имеет значение, в чем состоит вся работа. Или я может быть совершенно не понимаю его. Можете ли вы опубликовать некоторые входные и выходные данные - источник и то, что вы хотите получить?   -  person zdim    schedule 29.06.2016
comment
@zdim См. правки. Вход в unpack — это $str. Вывод на pack должен быть $str (или аналогичный).   -  person jrw32982    schedule 29.06.2016
comment
@zdim Возможно, это неясно, но unpack анализирует $str в порядке справа налево вместо обычного порядка слева направо. Это прекрасно работает. Но когда я пытаюсь использовать pack для воссоздания исходного $str, предоставляя поля для packed, скажем, в порядке справа налево, конструкция формата позиционирования @POS, которая отлично работала с unpack, с треском провалилась для воспроизведения того же исходного значения $str ( или аналогичный) с pack.   -  person jrw32982    schedule 29.06.2016
comment
Первый комментарий @JohnWiersba zdim - это ваш ответ: пакет этого не делает. Если вы измените свой вопрос на то, как мне это сделать в perl? есть конечно много способов. Даже способы, которые могут отключить шаблон пакета, если это необходимо. Но если вопрос в том, как мне это сделать с пакетом? ответ - нет :)   -  person hobbs    schedule 29.06.2016
comment
@JohnWiersba То, что я опубликовал, отражает мое текущее понимание того, о чем вы думаете. (Это substr функциональность, если я прав.) Писать в комментариях не имело смысла - пожалуйста, дайте мне знать, относится ли это к вашей задаче и могу ли я добавить какие-то другие решения.   -  person zdim    schedule 29.06.2016


Ответы (2)


Вы не можете использовать pack для записи в определенных местах внутри строки. Он не перемещается по строке с помощью своего рода "cursor", положение которого можно изменить, а просто объединяет все переданное ему и записывает с ним новую строку.

pack TEMPLATE,LIST
Берет СПИСОК значений и преобразует его в строку, используя правила, заданные ШАБЛОНОМ. Результирующая строка представляет собой конкатенацию преобразованных значений. [...]

Далее на странице в документах также говорится

Вы должны самостоятельно выполнить любое выравнивание или заполнение, вставив, например, достаточное количество "x"es при упаковке. У pack и unpack нет возможности узнать, куда идут или откуда приходят символы, поэтому они обрабатывают вывод и ввод как плоские последовательности символов.

Вы можете, конечно, записать строку как хотите, но только перестроив свой шаблон (при попытке не по порядку он заполняет по мере необходимости в соответствии с @, начиная с нуля и, таким образом, перезаписывая для каждого значения), и записывая или заполнить "промежуточные позиции". Итак, вы можете сказать

my $str = "...hi...defgabc";
my $fmt = q{@12 a3 @8 a4 @3 a2};

my @parts = unpack $fmt, $str;
# Add to @parts and template what need be in between or change $fmt to get all
my $res = pack "A3A4A2", @parts;

Затем вы можете извлечь все части исходной строки, изменить их порядок или создать подходящую маску индекса и pack ее. Я понимаю, что вы это знаете и не хотите, но pack ничего не может сделать, кроме как выписать всю строку.

Что касается написания частей строки, это как раз работа substr. Так что, возможно, вы могли бы написать небольшой цикл, используя @fmt и/или @parts, в котором substr будет заменять последовательности заданной длины в нужных местах. Тем не менее, pack все сразу должно быть гораздо эффективнее.

person zdim    schedule 29.06.2016
comment
Спасибо, @здим. См. мой ответ ниже, чтобы узнать, как избежать substr и зацикливания. Я надеялся, что просто что-то упустил в документации pack, поскольку unpack было так просто использовать для извлечения полей с использованием неупорядоченной спецификации. - person jrw32982; 29.06.2016
comment
@JohnWiersba Хорошо, это то, что я имел в виду - вы можете обрабатывать вещи разными способами, и join и map подходят для такого рода манипуляций. Я предложил substr в соответствии с вашей первоначальной идеей, чтобы эффективно записывать части строки (это очень эффективно). Кроме того, учитывая его синтаксис, это должен быть достаточно чистый код. Или, поскольку вы стремились к pack, использовать его, но все части будут переставлены. Я могу добавить эти подходы к тому, что я опубликовал, если это будет полезно. - person zdim; 29.06.2016
comment
@JohnWiersba Они не одинаковы - unpack начинается со строки и, таким образом, может извлекать что-то (хотя это не основное его использование). Но pack должен написать новую строку, и ему некуда идти, кроме как объединить все части, которые он дал. Нужная вам функциональность предоставляется substr. - person zdim; 29.06.2016

Очевидно, pack не может сделать это напрямую. Вот один из способов сделать это, который позволяет избежать циклов и использования substr. Однако по сравнению с легкой понятностью unpacking это не очень удовлетворительно. Я надеялся, что неправильно понял что-то в документации pack, что действительно позволяет pack быть обратным unpack для размещения полей в строке packed.

use strict; use warnings;
my $str = "...hi...defgabc";
my @pos = (
   { pos => 12, len => 3 }, 
   { pos =>  8, len => 4 }, 
   { pos =>  3, len => 2 }, 
);
my $fmt = join " ", map { "\@$_->{pos} a$_->{len}" } @pos;
# q{@12 a3 @8 a4 @3 a2};

my @a = unpack $fmt, $str;
print "<$_>\n" for @a;
print "\n";

my @sorted_idxes =
   sort { $pos[$a]{pos} <=> $pos[$b]{pos}
       or $pos[$a]{len} <=> $pos[$b]{len} }
   0..$#pos;

my $sorted_fmt = join " ", 
   map { "\@$pos[$_]->{pos} a$pos[$_]->{len}" } @sorted_idxes;

my $out = pack $sorted_fmt, @a[@sorted_idxes];
$out =~ s/\0/./g;
print "$out\n";
person jrw32982    schedule 29.06.2016