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来计算。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |