Perl 中s/// 和 tr/// 的差別

分类:perl | 标签: s   tr  
2013-07-02 20:11 阅读(?)评论(0)

 

关键词 1perl 2tr///

此文完整连结 http://note.tc.edu.tw/203.html
文章欢迎转载,请尊重版权注明连结来源。

 

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

 

作者已禁止网友对该文进行评论