1.1.1 提出问题
你想要存取和修改一个字符串中的一部分,而不是全部。比如,你读取了一个固定宽的记录,然后想要提取其中单独的一个片段。
1.1.2 解决方案
函数substr可以帮你读取和写入一个字符串的一个特定的部分。
$value = substr($string, $offset, $count); #string:原始字符串,offset:起始位置,count:子字符串长度
$value = substr($string, $offset); #忽略子字符串长度时,子串就取到末尾
substr($string, $offset, $count) = $newstring; #把字符串中的特定部分替换成另一个字符串
substr($string, $offset, $count, $newstring); #功能同上
substr($string, $offset) = $newtail; #功能同上,不过替换到末尾
|
函数unpack只能进行读操作,但是如果你有很多子字符串要提取的话,它的速度会更快。
# 得到一个5个字节的字串,然后跳过3个字节
# 然后再得到两个8个字节的字串, 然后再得到知道末尾的字串;
# (这只能处理ascii数据而不是unicode数据)
($leading, $s1, $s2, $trailing) =unpack("A5 x3 A8 A8 A*", $data);#其中大写的A代表截取ascii字符,x代表跳过。
# 以每五个字节划分字符串
@fivers = unpack("A5" x (length($string)/5), $string);
# 把字符串切割成单字节字符的列表
@chars = unpack("A1" x length($string), $string);
|
1.1.2 讨论
在perl中字符串是一种基础的数据类型,他们不是一个基础类型的数据(比如在c语言中)。所以不能像在其他语言中那样使用数据的下标来访问字符串中单独的字符,而要去使用像substr或unpack这样的函数来访问字符串的字符和字串。
substr函数的第一个参数标示了你感兴趣的字串的开头的位置,如果它是正数那么从字符串开头开始计算,如果它是负数则从末尾开始计算,如果它是零,则字串从原始字符串开头开始。下一个参数代表字串的长度。
$string = "This is what you have"; # +012345678901234567890 Indexing forwards (left to right)
# 109876543210987654321- Indexing backwards (right to left)
# note that 0 means 10 or 20, etc. above
$first = substr($string, 0, 1); # "T"
$start = substr($string, 5, 2); # "is"
$rest = substr($string, 13); # "you have"
$last = substr($string, -1); # "e"
$end = substr($string, -4); # "have"
$piece = substr($string, -8, 3);
|
其实使用substr函数你可以做更多的事情,比如你可以改变字符串中的字串。因为substr函数是一个“左值函数”,也就是说,我们可以给它的返回值赋值。(还有一些函数也有这种特性,比如vec,pos和keys。其实想local,my,our也可以看成左值函数)。
$string = "This is what you have"; print $string; # $>This is what you have
substr($string, 5, 2) = "wasn't"; # change "is" to "wasn't"
# $>This wasn't what you have
substr($string, -12) = "ondrous";# "This wasn't wondrous"
# $>This wasn't wondrous
substr($string, 0, 1) = ""; # delete first character
# $>his wasn't wondrous
substr($string, -10) = ""; # delete last 10 characters
|
还可以使用=~,s///,m//,tr// 等操作符和substr函数配合使用,来达到只影响字符串一部分的目的。
#测试 =~
if (substr($string, -10) =~ /pattern/) { print "Pattern matches in last 10 characters\n"; }
# 在前五个字符中如果出现 "at" 就替换成 "is"
substr($string, 0, 5) =~ s/is/at/g;
#你甚至可以在一个表达式中使用几个substr来交换他们的值:
# 交换字符串中的第一个字符和最后一个字符
$a = "make a hat"; (substr($a,0,1), substr($a,-1)) = (substr($a,-1), substr($a,0,1)); print $a; # $>take a ham
|
虽然unpack不是左值的,但是如果你要一次截取很多值的话它的速度确实比substr快很多。通过指定一个模式字符串来给unpack截取定位。比如,小写的“x”后面跟一个数字,就可以跳过前面的一些字节,大写的“X”和一个数字就可以跳回到后面的一些字节,一个“@”可以跳过一个前面的字节。(如果你的数据包含unicode字串,那么就要小心使用这三个模式了;因为他们是严格面向字节的,所以在一个多字节数据中按字节移来移去就很危险的)
# 跳过6个字符后,截取6个字符。
$a = "To be or not to be"; $b = unpack("x6 A6", $a); # skip 6, grab 6
print $b; # $>or not
($b, $c) = unpack("x6 A2 X5 A2", $a); #跳过6个字符,截取2个字符,然后跳回5个字符,再截取2个字符
print "$b\n$c\n"; # $>or
|
有时你宁愿想象你的数据是被特定的列所分割的,例如,你可能想要在数据的额8,14,20,26,和30列的位置放置切割点。这些列是每一个域的开始位置。虽然你可以计算出相应的unpack模式字符串("A7 A6 A6 A6 A4 A*"),但是这对于懒惰的perl程序员似乎太累脑子了,所以我们让perl去替我们完成吧,使用下面的cut2fmt函数:
sub cut2fmt { my(@positions) = @_; my $template = ''; my $lastpos = 1; foreach $place (@positions) { $template .= "A" . ($place - $lastpos) . " "; $lastpos = $place; } $template .= "A*"; return $template; }
$fmt = cut2fmt(8, 14, 20, 26, 30); print "$fmt\n"; # $>A7 A6 A6 A6 A4 A*
|
强大的unpack函数不仅仅可以做文本处理,它还是字符和二进制数据之间的关口,(我们在处理二进制数据的时候长用到)
在这节菜单中,我们假设所有的数据的字符都是7-8比特,所以pack函数的字节操作都可以正常的运行。
1.1.4 参考
pack,unpack,和substr函数的说明可以参考perlfunc(1)和大骆驼书的29章; 使用cut2fmt函数在菜单1.24,使用unpack的二进制模式将在菜单8.24.
阅读(287) | 评论(0) | 转发(0) |