John Wiersba John Wiersba - 1 year ago 58
Perl Question

How to use perl's pack function to reorder fields

I'm trying to reorder fields when building a string using

, but I can't seem to get
to do what I want. For example, I want to populate a string with
at offset 12,
at offset 8, and
at offset 3 (and whatever, presumably space or
, 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
ing fields in any order out of a string. But for
ing, it
-fills and truncates as documented. Is there any way to stop it from
-filling and truncating without reordering the
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
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
"cursor" dynamically without filling in intermediate positions or truncating.

In the above code, I would be happy if the return value of
was the same as
with any byte for
(e.g. blank or

Answer Source

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 unpacking, 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 packed 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} }

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";