I'm trying to reorder fields when building a string using pack
, but I can't seem to get pack
to do what I want. For example, I want to populate a string with abc
at offset 12, defg
at offset 8, and hi
at offset 3 (and whatever, presumably space or \0
, at offsets 0-2 and 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";
'
This works fine for unpack
ing fields in any order out of a string. But for pack
ing, it \0
-fills and truncates as documented. Is there any way to stop it from \0
-filling and truncating without reordering the pack
template to produce the fields left-to-right?
This question comes up when reading a field specification from an external source. Of course it can be arranged for the pack
template to be produced in left-to-right order and resulting list can be reordered to match the external field specification. But it would sure be handy to reposition the pack
"cursor" dynamically without filling in intermediate positions or truncating.
In the above code, I would be happy if the return value of pack(...)
was the same as $str
with any byte for .
(e.g. blank or \0
).
Apparently there is no way for pack
to do that directly. Here is one way of doing it, which avoids looping and using substr
. However, compared with the easy comprehensibility of unpack
ing, it's not very satisfactory. I was hoping that I had misunderstood something in the pack
documentation that would really allow pack
to be the reverse of unpack
for placement of fields within the pack
ed string.
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";