Perl 中s/// 和 tr/// 的差别
说真的,要学好 perl 还真的不简单,因为 perl 的程序代码比C来得精简一半1,靠得就是在撰写时的大脑运作。程设师得花更多时间写出精简的 code,同时也要将「语意上的错误」减少到最低,这就是要靠经验的累积。废话不多说,先说s/// 置换的功能,s 是 substitute的意思:
s/原来字符串/目的字符串/修饰子
s/// 会默认搜索 $ _,找出「原来字符串」,换成「目的字符串」。该运算符返回匹配的数量或进行替换的数量,如果没有进行任何匹配,则返回0。
ex: 把 Who 换成 What
$str = "Who are you?"; $str =~ s/Who/What/;
要取得比对成功的个数,可以写 print $str =~ s/Who/What/; # 结果 1 但是 print $str;
What are you? 很有趣的差别,就算是老手也容易忘记,其实写成这样就容易种了 $cnt= ($str =~ s/Who/What/); #$cnt=1
s/// 只会预设置换第一个找到的目标字符串,假若想全部置换,就要加 g 修饰子:
$str = "What a wonderful wonderful world."; $str =~ s/w/W/g; # str = "What a Wonderful Wonderful World."
假设比对原来的字符串不分大小写的话,就可加 i 修饰子:
$str = "What a wonderful wonderful world."; $str =~ s/w/www/ig; # str = "www a wwwonderful wwwonderful wwworld."
假若我们要将比对到的字符串,前后加上单引号,这在其他语言是困难的事,对perl来说像吃x一样简单,这里一个特殊变量 $& 就是比对到的字符串:
$str = "What a wonderful wonderful world."; $str =~ s/w/'$&'/g; # $str = "What a 'w'onderful 'w'onderful 'w'orld."
s/// 里面的「目的字符串」是可以放入函式的,例如以下几个常用的函数:
uc($str) 把$str 全转成大写
lc($str) 把$str 全转成小写
ucfirst($str) 把$str 第一码转成大写
假设我想把找到的结果全转成大写,一般的置换就伤透脑筋了,可是perl提供了不错的解法,但是要使用函数,就得加上 e 修饰子:
$str = "What a wonderful wonderful world."; $str =~ s/w\w+/uc($&)/ge; # $str = "What a WONDERFUL WONDERFUL WORLD"
如果没加 e 修饰子,则函式会被当成字符串丢出来: What a uc(wonderful) uc(wonderful) uc(world)
再来比较麻烦的是字符串中的换行 \n,字符串中的换行字符 '\n' 被当成是一个字符来处理,所以假设一个具有换行字符的(多行的的字符串,希望比对时忽略那个换行字符,就要加上 s 修饰子:
$str = "What a wonder\nful wonderful world."; $str =~ s/wonder.?ful/www/g; print $str;
What a wonder ful www world
接下来加上 s 修饰子后,\n就等于是"一个字符",也等于'\n';否则未加s的情况则不属于一个字符,也就是和 '.' 比对不会成功: $str =~ s/wonder.?ful/www/sg;
What a www www world
如果我们坚持一定要和换行比对成功,则:注意没加 s $str =~ s/wonder\nful/www/g;
What a www wonderful world
另外比较少见的情形下会用到的 m 修饰子:一般比对时假设要找出字符串结尾的字符串,常会用变换字符 $,在带有换行的字符串中,变换字符 $只会比对最后的换行或是字符串结尾。如果希望 $能取得符合带有换行的字符串中,每个换行都视为结尾的话,就要加 m。这样形容比较抽象,看个范例:
$str = "line123\nline456\nline789"; $str =~ s/\d+$//g; <== 注意没有加 m print $str;
line123 line456 line <==只比对最后一个
$str =~ s/\d+$//mg; <== 注意现在加了 m print $str;
line <==每个换行符号都视为结尾 line line
写了这么多 s///,现在来说 tr///,tr 的语法和 s 好像一样,其实还差异满大的,tr 主要作为项目列表的置换:
tr/原来比对列表/目的比对列表/选项
perl 的 tr把置换的功能再扩张,虽然 s很强,可是也有做不到的事,例如今天要把大写换成小写,「同时」小写也换成大写,s就一筹莫展了,但 tr 可以搞定:
$str = "Aine123\nBine789"; $str =~ tr/a-zA-Z/A-Za-z/; print $str;
aINE123 bINE789
同时tr 也可以用来计算字数,只需要把自己换成自己就好,例如以下范例计算$doc出现的数字个数:
$doc="<78>Nov 3 11:20:01 163.17.44.1 crond[30367]: (root) CMD (LANG=C LC_ALL=C /usr/bin/mrtg /etc/mrtg/mrtg.cfg --lock-file /var/lock/mrtg/mrtg_l --confcache-file /var/lib/mrtg/mrtg.ok)";
print $doc=~ tr/0-9/0-9/;
结果:22
这里要注意,上式的 $doc 本身并没有改变,所以如果下一行 print $doc; 得到的结果还会是原字符串。所以如果要取得计算的字数,就得写成这样:
$count = $doc=~ tr/0-9/0-9/;
tr 还有一个别名,叫作 y/// 所以要把数字0和9互换,可以写成
$doc =~ y/09/90/; 或是写成(把 / 换成 |; $doc =~ y|09|90|;
接来看看 tr 的选项,tr 只有三个选项,英文是 perldoc 的说明,我把他的意思用我的话写出来:
c Complement the SEARCHLIST. <== 列表没写到的就补给他右边列表的最后一个字符 d Delete found but unreplaced characters. <== 对照表中没有的项目就删掉 s Squash duplicate replaced characters. <== 连续重复出现的字压成一个
来看看范例:
my $text = 'good cheese'; $text =~ tr/eo/eu/s; print "$text\n"; # 结果 gud chese ,连续重复出现的字已被压成一个
my $big = 'vowels are useful'; $big =~ tr/aeiou/AEI/d; # 注意看对照表左边只有三个字母,所以如果遇到 ou,就会被删掉 print "$big\n"; # 结果 vwEls ArE sEfl
合并以上两个参数 my $text = 'good cheese'; $text =~ tr/eogd/eu/ds; # 写成 ds 或sd 都可以,顺序不重要 print "$text\n"; # 结果 u chese
最后来看 c 这个选项,c 较复杂不易懂,大致以我的大脑所知的说明如下:
tr/左列表/右列表/c
规则、左列表没有的,就补右清单的东西。
my $text = 'good cheese'; $text =~ tr/eo/_/c; #注意看对照表右边只有二个字母,只要右边列表没列到的就补'_',包括空白 print "$text\n";
# 结果 _oo____ee_e
如果右清单写了不只一个呢?会怎样?其实补的时候还是只会拿右清单的最后一个
$doc="<78>Nov 3 11:20:01 163.17.44.1 crond[30367]"; $doc =~ y/a-zA-Z/a-z/c;
# 结果不是英文全都补了右列表的最后一个字符'z' $doc= # zzzzNovzzzzzzzzzzzzzzzzzzzzzzzzzcrondzzzzzzz
当然不能都出简单的范例,来看看这个所以以下范列就可以把字符串中非英文字母a-zA-Z给删掉,成为空白。
$doc="<78>Nov 3 11:20:01 163.17.44.1 crond[30367]: (root) CMD (LANG=C LC_ALL=C /usr/bin/mrtg /etc/mrtg/mrtg.cfg --lock-file /var/lock/mrtg/mrtg_l --confcache-file /var/lib/mrtg/mrtg.ok)"; $doc =~ y/a-zA-Z/ /c;
# 结果 Nov crond root CMD LANG C LC ALL C usr bin mrtg etc mrtg mrtg cfg lock file var lock mrtg mrtg l confcache file var lib mrtg mrtg ok 如果要把上例中连续空白变一个,就同时用 sc 就好了,结果略。 $doc =~ y/a-zA-Z/ /sc;
当然还要写一点不一样的,要不然我写这篇就没意思了。现在如果要把变量的结果放到 tr 里,该怎么写?因为tr的清单是在编译时就写完了,如果要在执行期把演算出来的结果放去清单,就要写成这样(可参考 perlop )
eval "tr/$oldlist/$newlist/"; die $@ if $@;
预设缺省是用 $_ 去评估,所以以下范例:
$doc="<78>Nov 3 11:20:01 163.17.44.1 crond[30367]"; my $leftlist="a-zA-Z"; $_=$doc; eval "tr/$leftlist/ /cs, 1" or die $@; print $_."\n" ;
# 结果 Nov crond
结论,s/// 和 tr/// 还有很多用法,不过两者不太一样,要能活用,得花时间,我也没完全搞清"此"。
原始连结: 精赞部落 note.tcc.edu.tw http://note.tcc.edu.tw/content.php?sn=203 |
评论 想第一时间抢沙发么?