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

时间:2016-06-28 23:07:53

标签: perl pack unpack

我在使用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";
'

这适用于unpack字段中任何顺序的字段。但是对于pack,它\0 - 填充和截断为documented。有没有办法阻止它\0 - 填充和截断而不重新排序pack模板以从左到右生成字段?

从外部源读取字段规范时会出现此问题。当然,可以安排pack模板以从左到右的顺序生成,并且可以重新排序结果列表以匹配外部字段规范。但是,动态地重新定位pack“光标”而不填写中间位置或截断将是非常方便的。

在上面的代码中,如果pack(...)的返回值与$str的返回值与.的任何字节相同(例如空白或\0),我会很高兴

2 个答案:

答案 0 :(得分:2)

您不能在字符串内的特定位置写pack。它不会移动一个带有“ cursor ”的字符串,这些字符串可以重新定位 - 它会在编写新字符串时连接给它的所有内容。

  

打包模板,列表
  获取值的LIST并使用TEMPLATE给出的规则将其转换为字符串。结果字符串是转换值的串联。

在页面的下方,文档也说

  

您必须自己进行任何对齐或填充,例如,在包装时插入足够的“x”。打包和解包无法知道字符的来源或来源,因此它们处理输出并输入为平面字符序列。

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

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一次性更高效。

答案 1 :(得分:1)

显然pack无法直接执行此操作。这是一种方法,它可以避免循环和使用substr。然而,与unpack的易于理解相比,它并不十分令人满意。我希望我误解了pack文档中的某些内容,这些内容确实允许packunpack相反,以便在pack ed字符串中放置字段。< / p>

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