如何使用 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";
'
这适用于字符串中任何顺序的unpack
ing 字段。但是对于pack
ing,它\0
-填充并截断为documented。有没有办法阻止它从\0
-填充和截断而不重新排序pack
模板以从左到右生成字段?
从外部来源读取字段规范时会出现此问题。当然,可以安排pack
模板以从左到右的顺序生成,并且可以重新排序结果列表以匹配外部字段规范。但是动态重新定位pack
“光标”肯定会很方便,而无需填充中间位置或截断。
在上面的代码中,如果pack(...)
的返回值与$str
相同,并且.
有任何字节(例如空白或\0
),我会很高兴。
【问题讨论】:
我不认为pack
可以简单地尊重字符串中其他位置的内容,如果这就是你的意思的话。它连接起来。这是substr
的工作(但当时只有一个)。
那么在3
和hi
和8
之间,您想在其他地方做什么? $str
的原件?那么那些被解包的范围会发生什么——它们是否也保持原样(所以它们在输出字符串中被复制)?
@zdim 在这一点上,我并不真正关心其他职位。这个问题是关于如何根据包模板重新排列 pack
参数中字段位置的一般问题。当然,如果能够选择将它们设为空白或 NUL,那就太好了。
好的。但是,人们必须选择如何处理它们——或者,我们要写什么?如果是“重新订购”,则可能意味着 3-5
的内容现在位于 12-14
(反之亦然),这很好。或者,也许您想要“空白”(空格)。您是重新排序一个 16 字符长的字符串,还是从 a 字符串中挑选元素并写出一个 16 字符长的字符串,其余为 .... 零?空间?空(喘气)?有很好的解决方案,但整个工作是什么很重要。或者我可能完全误解了它。你能发布一些输入和输出——来源和你想要得到的东西吗?
@JohnWiersba zdim 的第一条评论就是你的答案:pack 不会那样做。如果您将问题修改为“我如何在 perl 中执行此操作?”当然有很多方法。甚至可以关闭包模板的方法,如果这是必要的话。但是,如果问题是“我如何使用包来做到这一点?”答案是“你没有”:)
【参考方案1】:
您不能让pack 写入字符串内的特定位置。它不会在带有“cursor”之类的可以重新定位的字符串周围移动——而是只是连接给它的所有内容并用它写入一个新字符串。
打包模板,列表 获取值列表并使用 TEMPLATE 给出的规则将其转换为字符串。结果字符串是转换后的值的串联。 [...]
在页面的下方,文档还说
您必须自己进行任何对齐或填充,例如在打包时插入足够的
"x"
es。pack
和unpack
无法知道字符的去向或来自何处,因此它们将输出和输入作为平面字符序列处理。
当然,你可以用你想要的任何方式写出字符串,但只能通过重新排列你的模板(如果尝试乱序,它会根据@
的需要填充,从零开始,从而覆盖每个值),并写出或填写“中间位置”。所以你可以说
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 很好,这是我的想法——你可以通过多种方式处理事情,join
和 map
适合这种操作。我建议substr
与您最初的想法保持一致,以有效地写入部分字符串(非常有效)。此外,鉴于它的语法,它应该是非常干净的代码。或者,因为您要使用 pack
,所以要使用它,但会重新排列所有部分。如果有用的话,我可以将这些方法添加到我发布的内容中。
@JohnWiersba 它们不一样——unpack
以字符串开头,因此 可以 拉出东西(虽然这不是它的主要用途)。但是pack
必须写出一个新字符串,它无处可去,只能连接它给出的所有片段。你想要的功能由substr
提供。【参考方案2】:
显然pack
无法直接执行此操作。这是一种避免循环和使用substr
的方法。不过和unpack
ing的通俗易懂相比,就不是很令人满意了。我希望我误解了pack
文档中的某些内容,这些内容实际上允许pack
与unpack
相反,以便在pack
ed 字符串中放置字段。
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 函数对字段重新排序的主要内容,如果未能解决你的问题,请参考以下文章