加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

perl pack和unpack的使用详解

发布时间:2020-12-16 00:09:19 所属栏目:大数据 来源:网络整理
导读:perl pack和unpack的使用方法 Pack 与unpack使用说明:? pack可视为将一系列的片段的数值打包在一起,可用于对dev档案、socket、memory的读写, 因为这些需要一块完整的memory,而且需要事先打包成特定格式; 而unpack可以视为将将这些完整的 memory切割计算
perl pack和unpack的使用方法

Pack 与unpack使用说明:?
pack可视为将一系列的片段的数值打包在一起,可用于对dev档案、socket、memory的读写,
因为这些需要一块完整的memory,而且需要事先打包成特定格式;
而unpack可以视为将将这些完整的 memory切割计算,取得我们所需要各部分的Variable。
例子如下:?
print pack(“H2”x10,map{ “3$_” } (0..9);?
得到?
0123456789?
因为ASCII中30~39代表数字0-9。所以pack后可以得到ascii编码的0->9,
而我们可以利用unpack将string作逆向操作。?
print unpack(“H*”,”0123456789”);?
30313233343536373839?
同样的作法可以用在中文字中,至是你要注意OS的编码格式,UTF-8、GB2312、Big5得到的数值并不会相同。
Pack与unpack也可以用在对于对于固定格式的文件作处理的情形下:?
代码:
Date ? ? ? |Description ? ? ? ? ? ? | Income ? ? ? ? |Expenditure?
01/24/2001 Ahmed's Camel Emporium ? ? 1147.99?
01/28/2001 Flea spray ? ? ? ? ? ? ? ? 24.99?
01/29/2001 Camel rides to tourists ? ?235.00
看到上面格式,想必大家很多人都用用substr将固定字段中的字符串取出来,
或利用有点点复杂的Regular Expression将上面的文件字段匹配出来,
而实际上我们要将上面字段一个一个匹配出来可以用一行搞定?
例子:?
while(){?
? my($date,$desc,$income,$expend) = unpack(“A10xA27xA7xA*”);?
}?
简单说明:
? A10: A表示ASCII,A10表示10个ASCII character,Date的表示就是用10个ASCII码;
? x ?: x表示null byte也等于skip a byte,也就是说我们要跳过一个char(|),
? A27: 然后接着27个ASCII char,
? x ?: 然后跳过一个vhar,
? A7 : 再接上7个ASCII,
? x ?: 在跳过一个char,
? A* : 最后A*表示不管后面char有多少个,全含括进来。
这样子就可以得到各个字段相对应的数据,很简单吧!:) 不需要什么太多的技巧,一行搞定。
之后将income与expend作加总,得到total_income、total_expend,数值,然后再输出的时候,
如果也要格式对称,要怎么办?
大多数人都会用format或sprintf,然后一行一行很辛苦的,将结果输出。?
? $income = sprintf("%.2f",$income); # Get them into?
? $expend = sprintf("%.2f",$expend); # "financial" format?
其实可以简化成:将$income与$expend先格式化,然后同上例将结果输出:?
? print pack("A11 A28 A8 A*",$date,"Totals",$expend);?
注意:这边多加一个char主要是因为’x’表示的是null byte,所以如果用A7xA27xA7xA*,
则输出的字符串会连在一起,而多给他一个char,就可以让字符串不会都连在一起。?
完成程序如下:?
#!/usr/bin/perl?
use POSIX;?
open(FF,"tt.txt");?
? my ($date,$expend) = unpack("A10xA27xA7xA*",$_);?
? $tot_income = $income;?
? $tot_expend = $expend;?
? $expend = sprintf(".2f","$desc",$expend),"n";?
$tot_income = sprintf("%.2f",$tot_income); # Get them into?
$tot_expend = sprintf(".2f",$tot_expend); # "financial" format?
$date = POSIX::strftime("%m/%d/%Y",localtime);?
print pack("A11 A28 A8 A*",$tot_income,$tot_expend),"n";
整数的pack:?
对于整数需要注意各个OS所定义的int的长度,与各个OS所用的byte order顺序是little-endian还是big-endian,
就是高位在前还是低位在前的意思。?
my $ps = pack(‘s’,20302);?
s:a signed short intger,一般都是16bits=2bytes,
如果我们将他print出来(打印这些pack后的字符,实际上是不具任何意义的),
会发现他等于NO或ON,然后将$ps在unpack可以得到20302?
unpack(‘s’,’NO’); ----------------->20302
注意:如果今天你用”s”,pack一个大于65535的数字,高位会自动被删除,
而得到与你想要的数字不同的结果,这点需要注意。
“l”: signed 32bits integer?
“L”:unsigned 32bits integer?
“q”:signed 64bits intger?
“Q”:unsigned 64bits integer?
“i”:signed integers of “local custom” variety?
“I”:unsigned integers of “local custom” variety?
这两个”i”与”I”主要与机器的OS定义有关系,其长度等于c中的sizeof(int),
如果要用于perl与其他语言的沟通,最好使用这个编码原则。?
对照表:(其中%Config要先use Config;才能使用)?
signed unsigned byte length in C byte length in Perl?
s! S! sizeof(short) $Config{shortsize}?
i! I! sizeof(int) $Config{intsize}?
l! L! sizeof(long) $Config{longsize}?
q! Q! sizeof(longlong) $Config{longlongsize}
对memory stack作pack:?
例子如下:memory stack长得像下面这样,接着利用unpack将stack中的各个字段取出。?
--------- ---- ---- ---------?
TOS: | IP |TOS 4:| FL | FH | FLAGS TOS 14:| SI |?
| CS | | AL | AH | AX | DI |?
| BL | BH | BX | BP |?
---- ---- ---------?
| CL | CH | CX | DS |?
| DL | DH | DX | ES |?
---- ---- ---------
my( $ip,$cs,$flags,$ax,$bx,$cd,$dx,$si,$di,$bp,$ds,$es ) = unpack( 'v12',$frame );
‘v’ :unsigned short in ‘VAX ‘ order?
这是取出横列的IP、CS、FLAGS、AX、BX、CX、DX、SI、DI、BP、DS、ES?
my( $fl,$fh,$al,$ah,$bl,$bh,$cl,$ch,$dl,$dh ) =?
unpack( 'C10',substr( $frame,4,10 ) );
‘C’:unsign character value,用于bytes读取?
这样子分两行很麻烦,所以将他们放在一起得到:?
$flags,$fl,helvetica; line-height:18px">$ax,$cx,$dh,helvetica; line-height:18px">$si,$es ) =?
unpack( 'v2' . ('vXXCC' x 5) . 'v5',$frame );?
‘X’:Backup a byte
网络应用:?
‘n’: An unsigned short in "network" (big-endian) order?
‘N’: An unsigned long in "network" (big-endian) order?
在作网络链接时,往往需要将长度先送给Server,使其知道后面有多少character要读取,
因此如果有一段msg,可以利用下列方式打包:?
? my $buf = pack( 'N',length( $msg ) ) . $msg;?
说明:等于「长度」( unsigned long)加上讯息内容,也可简化为:?
? my $buf = pack(‘NA*’,length($msg),$msg);?
然后将$buf送至对方server。同样对方可用unpack(‘NA*’,$buf)取得送出的数据。
Floating Point Numbers:?
‘f’: float (A single-precision float in the native format)?
‘d’:double(A double-precision float in the native format)?
对于浮点数(float point)可以使用f或d来作pack与unpack动作。
奇特的例子:?
Bit Strings:?
String中都是0或1,对其作pack/unpack的转换,需要注意那一系列的0/1,
与每八个bit一个字符的顺序性。假设今天有个string等于 ”10001100”可以用下列方式:?
$byte = pack( 'B8','10001100' ); # start with MSB?
$byte = pack( 'b8','00110001' ); # start with LSB
b A bit string (ascending bit order inside each byte,like vec()).(渐增)?
B A bit string (descending bit order inside each byte).(渐减)?
如果要对string中的一部份bits作pack/unpack是不可能的,因为pack会从最旁边开始,
然后以8个bits为一组,如果不足八个则补0。?
----------------- -----------------?
| S Z - A - P - C | - - - - O D I T |?
MSB LSB MSB LSB
例子:如上图中「-」表示保留的bit,不使用。
将上列这2 bytes 转换成一个string,可以利用’b16’来作:?
#!usr/bin/perl?
$status = "1101010100001111";?
$ps= pack('b16',$status);?
print "$psn";?
$status=unpack('b16',$ps);?
print "$statusn";?
@aa = split(//,helvetica; line-height:18px">print "@aan";?
#---简化?
#---可取得各个bit的数值?
($carry,undef,$parity,$auxcarry,$sign,helvetica; line-height:18px">$trace,$interrupt,$direction,$overflow) =?
split( //,unpack( 'b16',$status ) );
如果使用’b12’则,后面的4个bit会被忽略并不会被使用。
Uuencode:(Unix-To-Unix Encode)?
如果对于UNIX很熟悉的,应该知道uuencode与uudecode是干什么用的,
他可视为早期UNIX之间互相传送数据,所用的一种编码方法,将文件编码,
以便利于在早期的网络上传送数据,现在很少使用,但是有时还是有人会用到,
编码原理:取出三个bytes,将之切割成6份,每份4个 bits,然后在后面填入x20,
如此直到整个文件编码完成。
Pack可使用’u’作这件事情。
my $uubuf = pack( 'u',$bindat );
计算总和:(Do Sums)?
pack中有个特殊的template,%专门用来计算总和的,但是他不能在pack中使用,
而且他只能用于其他数字的前置位置(prefix)。
用于数字时:?
my $buf = pack( 'iii',100,20,3 );?
print unpack( '2i3',$buf ),"n"; # prints 123?
用于字符串时:?
print unpack( '2A*',"x01x10" ),"n"; # prints 17?
上面两个例子,%会将后面i3、A*加总起来,得到最后结果123与17,这可以用来得到最后的check sum。
另外不要太相信他所得到的数值,因为他们无法保证正确(?)。?
用来取得netmask的bits数目:?
my $bitcount = unpack( '2b*',$mask );
my $evenparity = unpack( ' *',$mask );?
取得evenparity的数值…mask=pack(‘b*’,”22222222222222211…0”);
Unicode:?
$UTF8{Euro} = pack( 'U',0x20AC );
欧洲用字编码x20AC,其UTF8的编码字符为$URF{Euro}。?
# pack and unpack the Hebrew alphabet?
my $alefbet = pack( 'U*',0x05d0..0x05ea );?
my @hebrew = unpack( 'U*',$utf );?
用于pack/unpack unicode编码
另一种Portable编码:?
w A BER compressed integer. Its bytes represent an unsigned integer in base 128,helvetica; line-height:18px">most significant digit first,with as few digits as possible. Bit eight (the high bit)?
is set on each byte except the last.
my $berbuf = pack( 'w*',1,128,128 1,128*128 127 );
长度与宽度(Length and Width)?
字符串长度(String Length)?
在传送网络数据时,往往需要将数据的长度放在封包的标头处,让对方Server知道后续还有多少数据需要处理:
下列例子为两个null terminated字符串加上数据长度,再加上数据,得到最后要送出的数据。
?
Z:A null terminated (ASCIZ) string,will be null padded.?
my $msg = pack( 'Z*Z*CA*',$src,$dst,length( $sm ),$sm );
而要取出数据可以用下列方式:?
( $src,$len,$sm ) = unpack( 'Z*Z*CA*',$msg );
但是,如果我们在pack后面加上另一个C,则在unpack时,会无法正确得到数据。
因为A*会把所有的剩余character都抓给$sm,而使得$prio变成无定义。?
# pack a message?
my $msg = pack( 'Z*Z*CA*C',$sm,$prio );
# unpack fails - $prio remains undefined!?
因此可以使用下列方式取得数据:?
# pack a message: ASCIIZ,ASCIIZ,length/string,byte?
my $msg = pack( 'Z* Z* C/A* C',helvetica; line-height:18px"># unpack?
加上”/”符号,可以使得perl在pack A*会记住$sm的长度,然后后续的$prio就可以区别出来储存,
而在unpack时,就可以依照$sm的长度,而将$sm的数据取出,
剩下来的就是$prio的数据。”/”只有在后面接上A*、a*、Z*时有意义。?
“/”表示后面A*的长度,占1个byte?
“/”可以代表任何数字,用以表示其后A*的长度?
# pack/unpack a string preceded by its length in ASCII?
my $buf = pack( 'A4/A*',"Humpty-Dumpty" );?
# unpack $buf: '13 Humpty-Dumpty'?
my $txt = unpack( 'A4/A*',$buf );
“/”在perl 5.6以后才出现,之前版本并不支持。针对旧版本需要做如下修改已取得长度。?
my $msg = pack( 'Z* Z* C A* C',length $sm,helvetica; line-height:18px">( undef,$len) = unpack( 'Z* Z* C',$msg );#--先取得长度?
#--依照长度设定A的长度?
($src,$prio) = unpack ( "Z* Z* x A$len C",helvetica; line-height:18px">Dynamic Template?
从前面到现在我们看到的都是有固定长度的范例,但是如果碰到没有固定长度的怎么办?以下有个例子来说明:?
my $env = pack( 'A*A*Z*' x keys( %Env ) . 'C',helvetica; line-height:18px">map( { ( $_,'=',$Env{$_} ) } keys( %Env ) ),0 );
?
为了方便让C来Parsing这个string,所以利用两个string与一个null terminated字符串,
利用map将$ENV中的参数读出来,然后利用(AAA,=,BBB)的方式,
储存成一个array,然后传给pack,然后 pack利用”A*A*Z*将AAA,BBB给打包起来,
keys(%ENV)表示ENV hash总共有多少个$element在里面。最后为了方便C parsing,在最后面加上一个”0”。?
my $n = $env =~ tr/// - 1;?
my %env = map( split( /=/,$_ ),unpack( 'Z*' x $n,$env ) );
而在unpack时,要先算出$env里面到底有多少个$element在里面,这可以透过tr来计算。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读