免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 20360 | 回复: 15
打印 上一主题 下一主题

pack/unpack用法----心得筆記 [复制链接]

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-10-03 19:00 |只看该作者 |倒序浏览
最近在看perlpacktut的document,整理了下面的文章,
希望對大家有幫助,瞭解pack/unpack怎麼用...
還沒寫完..後面跟C有關係..
======================================
Pack 與unpack使用說明:
  資料來源摘自perlpacktut,初學perl的時候,想必大家對於pack與unpack都不是很瞭解,因此本人擷取perldoc裡面的文章,為各位簡單說明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也可以用在對於對於固定格式的文件作處理的情形下:

  1.     Date        |Description                               | Income|Expenditure
  2.     01/24/2001 Ahmed's Camel Emporium                       1147.99
  3.     01/28/2001 Flea spray                                   24.99
  4.     01/29/2001 Camel rides to tourists                235.00  
复制代码


看到上面格式,想必大家很多人都用用substr將固定欄位中的字串取出來,或利用有點點複雜的Regular Expression將上面的文件欄位匹配出來,而實際上我們要將上面欄位一個一個匹配出來可以用一行搞定
例子:
while(<FF>;){
  my($date,$desc,$income,$expend) = unpack(“A10xA27xA7xA*”);
}
簡單說明:A表示ASCII,A10表示10個ASCII character,x表示null byte也等於skip a byte,也就是說我們要跳過一個char(|),然後接著27個ASCII char,然後跳過一個vhar,再接上7個ASCII,在跳過一個char,最後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,就可以讓字串不會都連在一起。
完成程式如下:

  1. #!/usr/bin/perl
  2. use POSIX;
  3. open(FF,"tt.txt");
  4. while(<FF>;){
  5.   my ($date,$desc,$income,$expend) = unpack("A10xA27xA7xA*",$_);
  6.      $tot_income += $income;
  7.      $tot_expend += $expend;
  8.      $income = sprintf("%.2f", $income);
  9.      $expend = sprintf("%12.2f", $expend);
  10. print pack("A11 A28 A8 A*", $date, "$desc", $income, $expend),"\n";
  11. }
  12. $tot_income = sprintf("%.2f", $tot_income); # Get them into
  13. $tot_expend = sprintf("%12.2f", $tot_expend); # "financial" format
  14. $date = POSIX::strftime("%m/%d/%Y", localtime);
  15. 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;才能使用)

  1.    signed unsigned  byte length in C   byte length in Perl      
  2.      s!     S!      sizeof(short)      $Config{shortsize}
  3.      i!     I!      sizeof(int)        $Config{intsize}
  4.      l!     L!      sizeof(long)       $Config{longsize}
  5.      q!     Q!      sizeof(longlong)   $Config{longlongsize}  
复制代码

對memory stack作pack:
例子如下:memory stack長得像下面這樣,接著利用unpack將stack中的各個欄位取出。

  1.       +---------+           +----+----+                        +---------+
  2. TOS: |   IP      |TOS+4:| FL | FH | FLAGS  TOS+14:|   SI    |
  3.          +---------+           +----+----+                         +---------+
  4.       |   CS    |                 | AL | AH | AX                    |   DI    |
  5.       +---------+              +----+----+                         +---------+
  6.                                     | BL | BH | BX                     |   BP    |
  7.                                     +----+----+                         +---------+
  8.                                     | CL | CH | CX                     |   DS    |
  9.                                     +----+----+                         +---------+
  10.                                     | DL | DH | DX                       |   ES    |
  11.                                     +----+----+                          +---------+  
复制代码

  1. 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

  1.    my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) =
  2.      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。

  1.    +-----------------+-----------------+
  2.    | S Z - A - P - C | - - - - O D I T |
  3.    +-----------------+-----------------+
  4.     MSB           LSB MSB           LSB  
复制代码

例子:如上圖中「-」表示保留的bit,不使用。

將上列這2 bytes 轉換成一個string,可以利用’b16’來作:
#!usr/bin/perl
$status = "1101010100001111";
$ps= pack('b16',$status);
print "$ps\n";
$status=unpack('b16',$ps);
print "$status\n";
@aa = split(//,$status);
print "@aa\n";
#---簡化
#---可取得各個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,%<number>;專門用來計算總和的,但是他不能在pack中使用,而且他只能用於其他數字的前置位置(prefix)。

用於數字時:
    my $buf = pack( 'iii', 100, 20, 3 );
    print unpack( '%32i3', $buf ), "\n";  # prints 123  
用於字串時:
print unpack( '%32A*', "\x01\x10" ), "\n";  # prints 17  
上面兩個例子,%會將後面i3、A*加總起來,得到最後結果123與17,這可以用來得到最後的check sum。另外不要太相信他所得到的數值,因為他們無法保證正確(?)。
用來取得netmask的bits數目:
    my $bitcount = unpack( '%32b*', $mask );  


my $evenparity = unpack( '%1b*', $mask );
取得evenparity的數值…mask=pack(‘b*’,”11111111111111111…0”);

Unicode:
   $UTF8{Euro} = pack( 'U', 0x20AC );  


歐洲用字編碼\0x20AC,其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/\0// - 1;
   my %env = map( split( /=/, $_ ), unpack( 'Z*' x $n, $env ) );  

而在unpack時,要先算出$env裡面到底有多少個$element在裡面,這可以透過tr來計算。

论坛徽章:
0
2 [报告]
发表于 2003-10-03 20:31 |只看该作者

pack/unpack用法----心得筆記

谢谢,先收藏,慢慢学!

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
3 [报告]
发表于 2003-10-08 09:59 |只看该作者

pack/unpack用法----心得筆記

怎么转换成简体的啊?

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
4 [报告]
发表于 2003-10-08 10:13 |只看该作者

pack/unpack用法----心得筆記

最近在看perlpacktut的document,整理了下面的文章,
希望对大家有帮助,了解pack/unpack怎麽用...
还没写完..後面跟C有关系..
======================================
Pack 与unpack使用说明:
资料来源摘自perlpacktut,初学perl的时候,想必大家对於pack与unpack都不是很了解,因此本人撷取perldoc里面的文章,为各位简单说明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也可以用在对於对於固定格式的文件作处理的情形下:
  1.     Date        |Description                               | Income|Expenditure
  2.     01/24/2001 Ahmed's Camel Emporium                       1147.99
  3.     01/28/2001 Flea spray                                   24.99
  4.     01/29/2001 Camel rides to tourists                235.00  
复制代码

看到上面格式,想必大家很多人都用用substr将固定栏位中的字串取出来,或利用有点点复杂的Regular Expression将上面的文件栏位匹配出来,而实际上我们要将上面栏位一个一个匹配出来可以用一行搞定
例子:
while(<FF>{
my($date,$desc,$income,$expend) = unpack(“A10xA27xA7xA*”);
}
简单说明:A表示ASCII,A10表示10个ASCII character,x表示null byte也等於skip a byte,也就是说我们要跳过一个char(|),然後接着27个ASCII char,然後跳过一个vhar,再接上7个ASCII,在跳过一个char,最後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,就可以让字串不会都连在一起。
完成程式如下:
  1. #!/usr/bin/perl
  2. use POSIX;
  3. open(FF,"tt.txt");
  4. while(<FF>;){
  5.   my ($date,$desc,$income,$expend) = unpack("A10xA27xA7xA*",$_);
  6.      $tot_income += $income;
  7.      $tot_expend += $expend;
  8.      $income = sprintf("%.2f", $income);
  9.      $expend = sprintf("%12.2f", $expend);
  10. print pack("A11 A28 A8 A*", $date, "$desc", $income, $expend),"\n";
  11. }
  12. $tot_income = sprintf("%.2f", $tot_income); # Get them into
  13. $tot_expend = sprintf("%12.2f", $tot_expend); # "financial" format
  14. $date = POSIX::strftime("%m/%d/%Y", localtime);
  15. 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;才能使用)
  1.    signed unsigned  byte length in C   byte length in Perl        
  2.      s!     S!      sizeof(short)      $Config{shortsize}
  3.      i!     I!      sizeof(int)        $Config{intsize}
  4.      l!     L!      sizeof(long)       $Config{longsize}
  5.      q!     Q!      sizeof(longlong)   $Config{longlongsize}  
复制代码

对memory stack作pack:
例子如下:memory stack长得像下面这样,接着利用unpack将stack中的各个栏位取出。
  1.       +---------+           +----+----+                        +---------+
  2. TOS: |   IP      |TOS+4:| FL | FH | FLAGS  TOS+14:|   SI    |
  3.          +---------+           +----+----+                         +---------+
  4.       |   CS    |                 | AL | AH | AX                    |   DI    |
  5.       +---------+              +----+----+                         +---------+
  6.                                     | BL | BH | BX                     |   BP    |
  7.                                     +----+----+                         +---------+
  8.                                     | CL | CH | CX                     |   DS    |
  9.                                     +----+----+                         +---------+
  10.                                     | DL | DH | DX                       |   ES    |
  11.                                     +----+----+                          +---------+  
复制代码
  1. 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
  1.    my( $fl, $fh, $al, $ah, $bl, $bh, $cl, $ch, $dl, $dh ) =
  2.      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。
  1.    +-----------------+-----------------+
  2.    | S Z - A - P - C | - - - - O D I T |
  3.    +-----------------+-----------------+
  4.     MSB           LSB MSB           LSB  
复制代码

例子:如上图中「-」表示保留的bit,不使用。

将上列这2 bytes 转换成一个string,可以利用’b16’来作:
#!usr/bin/perl
$status = "1101010100001111";
$ps= pack('b16',$status);
print "$ps\n";
$status=unpack('b16',$ps);
print "$status\n";
@aa = split(//,$status);
print "@aa\n";
#---简化
#---可取得各个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,%<number>;专门用来计算总和的,但是他不能在pack中使用,而且他只能用於其他数字的前置位置(prefix)。

用於数字时:
my $buf = pack( 'iii', 100, 20, 3 );
print unpack( '%32i3', $buf ), "\n"; # prints 123
用於字串时:
print unpack( '%32A*', "\x01\x10" ), "\n"; # prints 17
上面两个例子,%会将後面i3、A*加总起来,得到最後结果123与17,这可以用来得到最後的check sum。另外不要太相信他所得到的数值,因为他们无法保证正确(?)。
用来取得netmask的bits数目:
my $bitcount = unpack( '%32b*', $mask );


my $evenparity = unpack( '%1b*', $mask );
取得evenparity的数值…mask=pack(‘b*’,”11111111111111111…0”);

Unicode:
$UTF8{Euro} = pack( 'U', 0x20AC );


欧洲用字编码\0x20AC,其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/\0// - 1;
my %env = map( split( /=/, $_ ), unpack( 'Z*' x $n, $env ) );

而在unpack时,要先算出$env里面到底有多少个$element在里面,这可以透过tr来计算。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
5 [报告]
发表于 2003-10-08 10:15 |只看该作者

pack/unpack用法----心得筆記

为了方便大陆的朋友们阅读,我用 Windows 的“中文转码器”转换成了简体。再次感谢 apile!

论坛徽章:
0
6 [报告]
发表于 2003-10-08 13:40 |只看该作者

pack/unpack用法----心得筆記

感谢apile 和 flw !

论坛徽章:
0
7 [报告]
发表于 2003-10-08 15:08 |只看该作者

pack/unpack用法----心得筆記

感谢apile flw兄
收藏先

论坛徽章:
0
8 [报告]
发表于 2003-10-08 21:44 |只看该作者

pack/unpack用法----心得筆記

感谢apile兄的好文。

pack/unpack 正是两个不甚了解的函数.

论坛徽章:
0
9 [报告]
发表于 2004-05-15 21:34 |只看该作者

pack/unpack用法----心得筆記

谢谢啦!!!

很有用!

论坛徽章:
0
10 [报告]
发表于 2005-11-17 13:26 |只看该作者
不错,正打算问问这个.有怎么一篇好文章.
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP