Chinaunix首页 | 论坛 | 博客
  • 博客访问: 808714
  • 博文数量: 296
  • 博客积分: 5376
  • 博客等级: 大校
  • 技术积分: 2298
  • 用 户 组: 普通用户
  • 注册时间: 2009-03-14 19:02
文章分类

全部博文(296)

文章存档

2023年(2)

2020年(2)

2018年(2)

2017年(26)

2016年(4)

2015年(19)

2014年(12)

2013年(26)

2012年(84)

2011年(50)

2010年(41)

2009年(28)

分类: PERL

2015-10-04 20:50:48

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", $income, $expend); 


注意:这边多加一个char主要是因为’x’表示的是null byte,所以如果用A7xA27xA7xA*,
则输出的字符串会连在一起,而多给他一个char,就可以让字符串不会都连在一起。 


完成程序如下: 
代码:
#!/usr/bin/perl 
use POSIX; 


open(FF,"tt.txt"); 
while(){ 
  my ($date,$desc,$income,$expend) = unpack("A10xA27xA7xA*",$_); 
  $tot_income = $income; 
  $tot_expend = $expend; 
  $income = sprintf("%.2f", $income); 
  $expend = sprintf(".2f", $expend); 
  print pack("A11 A28 A8 A*", $date, "$desc", $income, $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*", $date, "Totals", $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读取 
这样子分两行很麻烦,所以将他们放在一起得到: 
my( $ip, $cs, 
$flags,$fl,$fh, 
$ax,$al,$ah, $bx,$bl,$bh, $cx,$cl,$ch, $dx,$dl,$dh, 
$si, $di, $bp, $ds, $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(//,$status); 
print "@aan"; 
#---简化 
#---可取得各个bit的数值 
($carry, undef, $parity, undef, $auxcarry, undef, $sign, 
$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*’,”11111111111111111…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, 
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, $dst, $len, $sm ) = unpack( 'Z*Z*CA*', $msg );


但是,如果我们在pack后面加上另一个C,则在unpack时,会无法正确得到数据。
因为A*会把所有的剩余character都抓给$sm,而使得$prio变成无定义。 
# pack a message 
my $msg = pack( 'Z*Z*CA*C', $src, $dst, length( $sm ), $sm, $prio );


# unpack fails - $prio remains undefined! 
( $src, $dst, $len, $sm, $prio ) = unpack(


因此可以使用下列方式取得数据: 
# pack a message: ASCIIZ, ASCIIZ, length/string, byte 
my $msg = pack( 'Z* Z* C/A* C', $src, $dst, $sm, $prio );


# unpack 
( $src, $dst, $sm, $prio ) = unpack( 'Z* Z* C/A* C', $msg );


加上”/”符号,可以使得perl在pack A*会记住$sm的长度,然后后续的$prio就可以区别出来储存,
而在unpack时,就可以依照$sm的长度,而将$sm的数据取出,
剩下来的就是$prio的数据。”/”只有在后面接上A*、a*、Z*时有意义。 
“/”表示后面A*的长度,占1个byte 
# pack a message: ASCIIZ, ASCIIZ, length/string, byte 
my $msg = pack( 'Z* Z* C/A* C', $src, $dst, $sm, $prio );


# unpack 
( $src, $dst, $sm, $prio ) = unpack( 'Z* Z* C/A* C', $msg );


“/”可以代表任何数字,用以表示其后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以后才出现,之前版本并不支持。针对旧版本需要做如下修改已取得长度。 
# pack a message: ASCIIZ, ASCIIZ, length, string, byte (5.005 compatible) 
my $msg = pack( 'Z* Z* C A* C', $src, $dst, length $sm, $sm, $prio );


# unpack 
( undef, undef, $len) = unpack( 'Z* Z* C', $msg );#--先取得长度 
#--依照长度设定A的长度 
($src, $dst, $sm, $prio) = unpack ( "Z* Z* x A$len C", $msg );


Dynamic Template 
从前面到现在我们看到的都是有固定长度的范例,但是如果碰到没有固定长度的怎么办?以下有个例子来说明: 
my $env = pack( 'A*A*Z*' x keys( %Env ) . 'C', 
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来计算。
阅读(948) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~