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

Posted

技术标签:

【中文标题】如何使用 perl 的 pack 函数对字段重新排序【英文标题】:How to use perl's pack function to reorder fields 【发布时间】:2016-06-28 23:07:52 【问题描述】:

在使用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-填充并截断为documented。有没有办法阻止它从\0-填充和截断而不重新排序pack 模板以从左到右生成字段?

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

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

【问题讨论】:

我不认为 pack 可以简单地尊重字符串中其他位置的内容,如果这就是你的意思的话。它连接起来。这是substr 的工作(但当时只有一个)。 那么在3hi8 之间,您想在其他地方做什么? $str的原件?那么那些被解包的范围会发生什么——它们是否也保持原样(所以它们在输出字符串中被复制)? @zdim 在这一点上,我并不真正关心其他职位。这个问题是关于如何根据包模板重新排列 pack 参数中字段位置的一般问题。当然,如果能够选择将它们设为空白或 NUL,那就太好了。 好的。但是,人们必须选择如何处理它们——或者,我们要写什么?如果是“重新订购”,则可能意味着 3-5 的内容现在位于 12-14(反之亦然),这很好。或者,也许您想要“空白”(空格)。您是重新排序一个 16 字符长的字符串,还是从 a 字符串中挑选元素并写出一个 16 字符长的字符串,其余为 .... 零?空间?空(喘气)?有很好的解决方案,但整个工作是什么很重要。或者我可能完全误解了它。你能发布一些输入和输出——来源和你想要得到的东西吗? @JohnWiersba zdim 的第一条评论就是你的答案:pack 不会那样做。如果您将问题修改为“我如何在 perl 中执行此操作?”当然有很多方法。甚至可以关闭包模板的方法,如果这是必要的话。但是,如果问题是“我如何使用包来做到这一点?”答案是“你没有”:) 【参考方案1】:

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

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

在页面的下方,文档还说

您必须自己进行任何对齐或填充,例如在打包时插入足够的"x"es。 packunpack 无法知道字符的去向或来自何处,因此它们将输出和输入作为平面字符序列处理。

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

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-ing 一下子应该会更有效率。

【讨论】:

谢谢,@zdim。请参阅下面的答案,了解一种避免 substr 和循环的方法。我希望我只是在pack 文档中遗漏了一些东西,因为unpack 很容易使用乱序规范来提取字段。 @JohnWiersba 很好,这是我的想法——你可以通过多种方式处理事情,joinmap 适合这种操作。我建议substr 与您最初的想法保持一致,以有效地写入部分字符串(非常有效)。此外,鉴于它的语法,它应该是非常干净的代码。或者,因为您要使用 pack,所以要使用它,但会重新排列所有部分。如果有用的话,我可以将这些方法添加到我发布的内容中。 @JohnWiersba 它们不一样——unpack 以字符串开头,因此 可以 拉出东西(虽然这不是它的主要用途)。但是pack 必须写出一个新字符串,它无处可去,只能连接它给出的所有片段。你想要的功能由substr提供。【参考方案2】:

显然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/\0/./g;
print "$out\n";

【讨论】:

以上是关于如何使用 perl 的 pack 函数对字段重新排序的主要内容,如果未能解决你的问题,请参考以下文章

Perl 中的“打包”函数

如何在不实际拆包的情况下获得 Perl 中打包项目的数量?

如何利用php数组对百万数据进行排重

高效 pre-perl-5.10 等效于 pack("Q>")

perl中的pack与unpack

如何在 PHP 中解压缩二进制字符串?