如何使用 perl 的 pack 函数重新排序字段

How to use perl's pack function to reorder fields

我试图在使用 pack 构建字符串时对字段重新排序,但我似乎无法让 pack 执行我想要的操作。例如,我想用偏移量 12 处的 abc、偏移量 8 处的 defg 和偏移量 3 处的 hi 填充一个字符串(以及任何可能的 space 或 [= 16=], 在偏移量 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";
'

这适用于 unpack 字符串中任意顺序的字段。但是对于 packing,它 [=16=] 填充并截断为 documented。有什么方法可以阻止它从 [=16=] 填充和截断而不重新排序 pack 模板以生成从左到右的字段?

从外部源读取字段规范时会出现此问题。当然,可以安排 pack 模板以从左到右的顺序生成,结果列表可以重新排序以匹配外部字段规范。但是动态地重新定位 pack "cursor" 肯定会很方便,而无需填充中间位置或截断。

在上面的代码中,如果 pack(...) 的 return 值与 $str 相同且 . 的任何字节(例如空白或[=16=]).

您不能 pack 在字符串内的特定位置写入。它不会在带有可以重新定位的“光标”的字符串周围移动——而是只是连接给它的所有内容并用它写入一个新字符串。

pack TEMPLATE,LIST
Takes a LIST of values and converts it into a string using the rules given by the TEMPLATE. The resulting string is the concatenation of the converted values. [...]

在页面的下方,文档还说

You must yourself do any alignment or padding by inserting, for example, enough "x"es while packing. There is no way for pack and unpack to know where characters are going to or coming from, so they handle their output and input as flat sequences of characters.

当然,您可以以任何您想要的方式写出字符串,但只能通过重新安排您的模板(如果尝试乱序,它会根据 @ 根据需要填充,从零到从而覆盖每个值),并写出或填充“中间位置”。所以你可以说

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 and/or @parts 编写一个小循环,其中 substr 将在需要的位置替换给定长度的序列。然而,pack-一次全部处理应该更有效率。

显然 pack 无法直接这样做。这是一种方法,它避免了循环和使用 substr。不过与unpacking的易懂性相比,还是不太尽如人意。我希望我误解了 pack 文档中的某些内容,这些内容实际上允许 packunpack 相反,以便在 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/[=10=]/./g;
print "$out\n";