perl中的正则表达式

本文详细介绍了Perl语言中的正则表达式应用,包括三种形式的使用方法、常用模式、八大原则等内容,适合初学者入门。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

9    Perl 中的正则表达式

  • 正则表达式的三种形式

  • 正则表达式中的常用模式

  • 正则表达式的 8 大原则

 

      正则表达式是 Perl 语言的一大特色,也是 Perl 程序中的一点难点,不过如果大家能够很好的掌握他,就可以轻易地用正则表达式来完成字符串处理的任务,当然在 CGI 程序设计中就更能得心应手了。下面我们列出一些正则表达式书写时的一些基本语法规则。


9.1 正则表达式的三种形式

首先我们应该知道 Perl 程序中,正则表达式有三种存在形式,他们分别是:

匹配:m/<regexp>/ (还可以简写为 /<regexp>/ ,略去 m)

替换:s/<pattern>/<replacement>/

转化:tr/<pattern>/<replacemnt>/

这三种形式一般都和 =~ 或 !~ 搭配使用(其中 "=~" 表示相匹配,在整条语句中读作 does,"!~" 表示不匹配,在整条语句中读作 doesn't),并在左侧有待处理的标量变量。如果没有该变量和 =~ !~ 操作符,则默认为处理 $_ 变量中的内容。举例如下:

$str = "I love Perl";
$str =~ m/Perl/; # 表示如果在 $str 中发现 "Perl" 字符串,则返回 "1" 否则返回 "0"。
$str =~ s/Perl/BASH/; # 表示将变量 $str 中的 "Perl" 字符串替换为 "BASH",如果发生此替换则返回 "1",否则返回 "0"。
$str !~ tr/A-Z/a-z/; # 表示将变量 $str 中的所有大写字母转化为小写字母,如果转化发生了则返回 "0",否则返回 "1"。

另外还有:

foreach (@array) { s/a/b/; } # 此处每次循环将从 @array 数组中取出一个元素存放在 $_ 变量中,并对 $_ 进行替换处理。
while (<FILE>) { print if (m/error/); } # 这一句稍微复杂一些,他将打印 FILE 文件中所有包含 error 字符串的行。

Perl 的正则表达式中如果出现 () ,则发生匹配或替换后 () 内的模式被 Perl 解释器自动依次赋给系统 $1, $2 ...... 请看下面的例子:

$string = "I love perl";
$string =~ s/(love)/<$1>/; # 此时 $1 = "love",并且该替换的结果是将 $string 变为 "I <love> perl"
$string = "i love perl";
$string =~ s/(i)(.*)(perl)/<$3>$2<$1>/; # 这里 $1 = "i",$2 = " love ",$3 = "perl",并且替换后 $string 变为 "<perl> love <i>"

替换操作 s/<pattern>/<replacement>/ 还可以在末尾加上 e 或 g 参数,他们的含义分别为:

s/<pattern>/<replacement>/g 表示把待处理字符串中所有符合 <pattern> 的模式全部替换为 <replacement> 字符串,而不是只替换第一个出现的模式。
s/<pattern>/<replacement>/e 表示将把 <replacemnet> 部分当作一个运算符,这个参数用的不多。

比如下面的例子:

$string = "i:love:perl";
$string =~ s/:/*/; #此时 $string="i*love:perl";
$string = "i:love:perl";
$string =~ s/:/*/g; #此时 $string="i*love*perl";
$string =~ tr/*/ /; #此时 $string="i love perl";
$string = "www22cgi44";
$string =~ s/(\d+)/$1*2/e; # (/d+)代表 $string 中的一个或多个数字字符,将这些数字字符执行 *2 的操作,因此最后 $string 变成了 "www44cgi88"。

下面给出一个完整的例子:

#!/usr/bin/perl

print"请输入一个字符串!\n";
$string = <STDIN>; # <STIDN>代表标准输入,会让使用者输入一字符串
chop($string); # 将$string最后一个换行的字符\n删除掉
if($string =~ /perl/){
  print("输入的字符串中有 perl 这个字符串!\n";
}

如果输入的字符串含有 perl 这个字符串的话,就会显示后面的提示信息。

 

9.2 正则表达式中的常用模式

下面是正则表达式中的一些常用模式。

/pattern/ 结果
.匹配除换行符以外的所有字符
x?匹配 0 次或一次 x 字符串
x*匹配 0 次或多次 x 字符串,但匹配可能的最少次数
x+匹配 1 次或多次 x 字符串,但匹配可能的最少次数
.*匹配 0 次或一次的任何字符
.+匹配 1 次或多次的任何字符
{m}匹配刚好是 m 个 的指定字符串
{m,n}匹配在 m个 以上 n个 以下 的指定字符串
{m,}匹配 m个 以上 的指定字符串
[]匹配符合 [] 内的字符
[^]匹配不符合 [] 内的字符
[0-9]匹配所有数字字符
[a-z]匹配所有小写字母字符
[^0-9]匹配所有非数字字符
[^a-z]匹配所有非小写字母字符
^匹配字符开头的字符
$匹配字符结尾的字符
\d匹配一个数字的字符,和 [0-9] 语法一样
\d+匹配多个数字字符串,和 [0-9]+ 语法一样
\D非数字,其他同 \d
\D+非数字,其他同 \d+
\w英文字母或数字的字符串,和 [a-zA-Z0-9] 语法一样
\w+和 [a-zA-Z0-9]+ 语法一样
\W非英文字母或数字的字符串,和 [^a-zA-Z0-9] 语法一样
\W+和 [^a-zA-Z0-9]+ 语法一样
\s空格,和 [\n\t\r\f] 语法一样
\s+和 [\n\t\r\f]+ 一样
\S非空格,和 [^\n\t\r\f] 语法一样
\S+和 [^\n\t\r\f]+ 语法一样
\b匹配以英文字母,数字为边界的字符串
\B匹配不以英文字母,数值为边界的字符串
a|b|c匹配符合a字符 或是b字符 或是c字符 的字符串
abc匹配含有 abc 的字符串
(pattern)() 这个符号会记住所找寻到的字符串,是一个很实用的语法。第一个 () 内所找到的字符串变成 $1 这个变量或是 \1 变量,第二个 () 内所找到的字符串变成 $2 这个变量或是 \2 变量,以此类推下去。
/pattern/ii 这个参数表示忽略英文大小写,也就是在匹配字符串的时候,不考虑英文的大小写问题。
\如果要在 pattern 模式中找寻一个特殊字符,如 "*",则要在这个字符前加上 \ 符号,这样才会让特殊字符失效
 

下面给出一些例子:   

范例说明
/perl/找到含有 perl 的字符串
/^perl/找到开头是 perl 的字符串
/perl$/找到结尾是 perl 的字符串
/c|g|i/找到含有 c 或 g 或 i 的字符串
/cg{2,4}i/找到 c 后面跟着 2个到 4个 g ,再跟着 i 的字符串
/cg{2,}i/找到 c 后面跟着 2个以上 g ,再跟着 i 的字符串
/cg{2}i/找到 c 后面跟着 2个 g,再跟着 i 的字符串
/cg*i/找到 c 后面跟着 0个或多个 g ,再跟着 i 的字符串,如同/cg{0,1}i/
/cg+i/找到 c 后面跟着一个以上 g,再跟着 i 的字符串,如同/cg{1,}i/
/cg?i/找到 c 后面跟着 0个或是 1个 g ,再跟着 i 的字符串,如同/cg{0,1}i/
/c.i/找到 c 后面跟着一个任意字符,再跟着 i 的字符串
/c..i/找到 c 后面跟着二个任意字符,再跟着 i 的字符串
/[cgi]/找到符合有这三个字符任意一个的字符串
/[^cgi]/找到没有这三个字符中任意一个的字符串
/\d/找寻符合数字的字符,可以使用/\d+/来表示一个或是多个数字组成的字符串
/\D/找寻符合不是数字的字符,可以使用/\D+/来表示一个或是更多个非数字组成的字符串
/\*/找寻符合 * 这个字符,因为 * 在常规表达式中有它的特殊意思,所以要在这个特殊符号前加上 \ 符号,这样才会让这个特殊字符失效
/abc/i找寻符合 abc 的字符串而且不考虑这些字符串的大小写

 

9.3 正则表达式的八大原则

  如果在 Unix 中曾经使用过 sed、awk、grep 这些命令的话,相信对于 Perl 语言中的正则表达式(Regular Expression)不会感到陌生。Perl 语言由于有这个功能,所以对字符串的处理能力非常强。在Perl语言的程序中,经常可以看到正则表达式的运用,在 CGI 程序设计中也不例外。

  正则表达式是初学 Perl 的难点所在,不过只要一旦掌握其语法,你就可以拥有几乎无限的模式匹配能力,而且 Perl 编程的大部分工作都是掌握常规表达式。下面给大家介绍几条正则表达式使用过程中的 8 大原则。

  正则表达式在对付数据的战斗中可形成庞大的联盟——这常常是一场战争。我们要记住下面八条原则:

· 原则1:正则表达式有三种不同形式(匹配(m/ /),替换(s/ / /eg)和转换(tr/ / /))。

· 原则2:正则表达式仅对标量进行匹配( $scalar =~ m/a/; 可以工作; @array =~ m/a/ 将把@array作为标量对待,因此可能不会成功)。

· 原则3:正则表达式匹配一个给定模式的最早的可能匹配。缺省时,仅匹配或替换正则表达式一次( $a = 'string string2'; $a =~ s/string/ /; 导致 $a = 'string 2')。

· 原则4:正则表达式能够处理双引号所能处理的任意和全部字符( $a =~ m/$varb/ 在匹配前把varb扩展为变量;如果 $varb = 'a' $a = 'as',$a =~ s/$varb/ /; 等价于 $a =~ s/a/ /; ,执行结果使 $a = " s" )。

· 原则5:正则表达式在求值过程中产生两种情况:结果状态和反向引用: $a=~ m/pattern/ 表示 $a 中是否有子串 pattern 出现,$a =~ s/(word1)(word2)/$2$1/ 则“调换”这两个单词。

· 原则6:正则表达式的核心能力在于通配符和多重匹配运算符以及它们如何操作。$a =~ m/\w+/ 匹配一个或多个单词字符;$a =~ m/\d/" 匹配零个或多个数字。

· 原则7:如果欲匹配不止一个字符集合,Perl使用 "|" 来增加灵活性。如果输入 m/(cat|dog)/ 则相当于“匹配字符串 cat 或者 dog。

· 原则8:Perl用 (?..) 语法给正则表达式提供扩展功能。(这一点请同学们课后看相关资料)

想要学习所有这些原则?我建议大家先从简单的开始,并且不断的尝试和实验。实际上如果学会了 $a =~ m/ERROR/ 是在 $a 中查找子串ERROR,那么你就已经比在 C 这样的低层语言中得到了更大的处理能力。



第八章 更多關於正規表示式

8. 更多關於正規表示式
正規表示式確實能夠完成很多字串比對的工作,可是當然也需要花更多的時間去熟悉這個高深的學問。如果你從來沒有用過正規表示式,你可以在學Perl時學會用Perl,然後在很多其他Unix環境下的應用程式裡面使用。當然,如果你曾經用過正規表示式,那麼可以在這裡看到一些更有趣的用法。我們在上一章已經介紹了正規表示式的一些基本概念,千萬別忘記,那些只是正規表示式最基本的部份,因為Perl能夠妥善的處裡字串幾乎就是仰賴正規表示式的強大功能。所以我們要來介紹更多關於正規表示式的用法。
8.1 只取一瓢飲
當你真正使用了正規表示式去進行字串比對的時候,你會發現,有時候會有可選擇性的比對。比如我希望找「電腦」或「資訊」這兩個詞是否在一篇文章裡,也就是只要「電腦」或「資訊」中任何一個詞出現在文章裡都算是比對成功,那麼我們就應該使用管線符號/|/來表式。所以我們的樣式應該試寫成這樣:/電腦|資訊/。
還有可能,你會想要找某個字串中部份相等的比對,就像這樣:

/f(oo|ee)t/  		# 找 foot 或 feet
/it (is|was) a good choice/  # 在句子中用不同的字
/on (March|April|May)/  	# 顯然也可以多個選擇
8.2 比對的字符集合
在Perl中的所有的命名規則都必須以字母或底線作為第一個字元,那麼我們如果要以正規表示式來描述這樣的規則應該怎麼作呢?你總不希望你的樣式表達寫成這個樣子吧?

(/a|b|c|d|.......|z|A|B|C|D|......|_|)

這樣的寫法也確實是太過壯觀了一些。那麼我們應該怎麼減少自己跟其他可能看到這支程式的程式設計師在維護時的負擔呢?Perl提供了一種不錯的方式,也就是以「集合」的方式來表達上面的那個概念。因此剛剛的寫法以集合的方式來表達就可以寫成這樣:
[a-zA-Z_]
很顯然的,有些時候我們希望比對的字元是屬於數字,那麼就可以用[0-9]的方式。如果有需要,你也可以這麼寫[13579]來表示希望比對的是小於10的奇數。
有時候你會遇到一個問題,你希望比對的字元也許是各種標點,也就是你在鍵盤上看到,躲在數字上緣的那一堆字符,所以你想要寫成這樣的集合:
[!@#$%^&*()_+-=]
可是這時候問題就出現了,我們剛剛使用了連字號(-)來取得a-z,A-Z的各個字符,可是這裡有一個[+-=]會變成甚麼樣子呢?這恐怕會產生出讓人意想不到的結果。所以我們為了避免這種狀況,必須跳脫這個特殊字元,所以如果你真的希望把連字號放進你的字符集合的話,就必須使用(\-)的方式,所以剛剛的字符集合應該要寫成:
[!@#$%^&*()_+\-=]
另外,在字符集合還有一個特殊字元^,這被稱為排除字元。不過他的效用只在集合的開始,例如像是這樣:
[^24680]
這就表示比對24680以外的字元才算符合。

8.3 正規表示式的特別字元
就像我們在介紹正規表示式的概念的時候所說的,Perl是逐字元在處理樣式比對的。可是對於某一些字元,我們卻很難使用一般鍵盤上的按鍵去表達這些字元。所以我們就需要一些特殊字元的符號。這些就是Perl在處理正規表示式時常用的一些特殊字元:

\s:很多時候,我們回看到要比對的字串中有一些空白,可是很難分辨他們到底是空格,跳格符號或甚至是換行符號 (註一),這時候我們可以用\s來對這些字元進行比對。而且\s對於空白符號的比對掌握非常的高,他可以處理(\n\t\f\r )這五種字元。除了原來的空白鍵,以及我們所提過的跳格字元(\t),換行字元(\n)外,\s還會比對藉以表示回行首的\r跟換頁字元\f。
\S:在大部份的時候,正規表示式特殊字元的大小寫總是表示相反的意思,例如我們使用\s來表示上面所說的五種空白字元,那麼\S也就是排除以上五種字元。
\w:這個特殊字元就等同於[a-zA-Z]的字符集合,例如你可以比對長度為3到10的英文單字,那就要寫成:\w{3,10},同樣的,你就可以比對英文字母或英文單字了。
\W:同樣的,如果你不希望看到任何在英文字母範圍裡的字符,不妨就用這個方式避開。
\d:這個特殊的字元就是字符集合[0-9]的縮寫。
\D:其實你也可以寫成[^0-9],如果你不覺得麻煩的話。

這些縮寫符號也可以放在中括號括住的集合內,例如你可以寫成這樣:[\d\w_],這就表示字母,數字或底線都可以被接受。而且看起來顯然比起[a-zA-Z0-9_]舒服多了。
另外,你也可以這麼寫[\d\D],這表示數字或不是數字,所以就是所有字元,不過既然要全部字元,那就不如用"."來表示了。

8.4 一些修飾字元
現在是不是越來越進入裝況了呢?我們已經可以使用一般的比對樣式來對需要的字串進行比較了。於是我們拿到了一篇文字,就像這樣:

I use perl and I like perl. I am a Perl Monger. 

我們現在希望找出裡面關於Perl的字串,這樣該相當簡單,所以我們把這串文字定義為字串$content。然後只要用這樣的樣式來比對:

$content =~ /perl/;

不過好像不太對勁,或許我們應該改寫成這樣:

$content =~ /Perl/;

可是萬一我們打算從檔案裡面取出一篇文字,然後去比對某個字串,這時候我們不知道自己會遇到的是Perl或perl。既然如此,我們可以用字符集合來表示,就像我們之前說過的樣子:

$content =~ /[pP]erl/;

可是我要怎麼確定不會寫成PERL呢?其實你可以考慮忽略大小寫的比對方式,所以你只要這樣表示:

$content =~ /perl/i;

其中的修飾字元i就是告訴Perl,你希望這次的比對可以忽略大小寫,也就是不管大小寫都算是比對成功。所以你有可能比對到Perl,perl,PERL。當然也可能有pErL這種奇怪的字串,不過有時候你會相信沒人會寫出這樣的東西在自己的文章裡。
Perl在進行比對的修飾字元,除了/i之外,我們還有/s可用。我們剛剛稍微提到了可以使用萬用字元點號(.)來進行比對,可是使用萬用字元卻有一個問題,也就是如果我們拿到的字串不在同一行內,萬用字串是沒辦法自動幫我們跨行比對,就像這樣:

my $content = "I like perl. \n I am a perl monger. \n";
if ($content =~ /like.*monger/) {
    print "*$1*\n";
}

我們想要找到like到monger中間的所有字元,可是因為中間多了換行符號(\n),所以Perl並不會找到我們真正需要的東西。這時候我們就可以動用/s來要求Perl進行跨行的比對。因此我們只要改寫原來的樣式為:

$content =~ /like.*monger/s

那麼就可以成功的進行比對了。可是如果有人還是喜歡用Perl Monger或是PERL MONGER來表達呢?我們當然還是可以同時利用忽略大小寫的修飾字元,因此我們再度重寫整個比對樣式:

$content =~ /like.*monger/is

這兩個修飾字元對於比對確實非常有用。

8.5 取得比對的結果
雖然樣式比對的成功與否對我們非常有用,可是很多時候我們並無法滿足於這樣的用法。尤其當我們使用了一些量詞,或修飾字元之後,我們還會希望知道自己到底得到了甚麼樣的字串。就以剛剛的例子來看,我的比對樣式是表示從like開始,到monger結束,中間可以有隨便任何字元。可是我要怎麼知道我到底拿到了甚麼呢?這時候我就需要取得比對的結果了。
Perl有預設變數來讓你取得比對的結果,就是以錢號跟數字的結合來表示,就像這樣:($1,$2,$3....)。
而用法也相當簡單,你只要把需要放入預設變數的比對結果以小括號刮起括就可以了,就以我們剛剛的例子來看,你只要改寫比對樣式,就像這樣:

my $content = "I like perl. \n I am a perl monger. \n";
if ($content =~ /(like.*monger)/s) {
    print "$1\n";
}

這裡的$1就是表示第一個括號括住的的比對結果。所以Perl會送出這樣的結果:

[hcchien@Apple]% perl ch3.pl
like perl. 
 I am a perl monger

當然,預設的比對變數也是可以一次擷取多個比對結果,就像下面的例子:

my $content = "I like perl. \n I am a perl monger. \n";
if ($content =~ /(perl)\s(monger)/s) {     	# $1 = "perl", $2 = "monger"
    print "$1\n";  			# 印出 perl
}


不過我們如果再把這個小程式改寫成這樣呢?


my $content = "I like perl. \n I am a perl monger. \n";
if ($content =~ /((perl)\s(monger))/s) {
    print "$1\n$2\n$3\n";
}

結果非常有趣:

[hcchien@Apple]% perl ch3.pl
perl monger
perl
monger

看出來了嗎?我們用括號拿到三個比對變數,而Perl分配變數的方式則是根據左括號的位置來進行。因此最左邊的括號是整個比對結果,也就是"perl monger",接下來是"perl",最後才是"monger"。相當有趣,也相當實用。
不過在使用這些暫存變數有一些必須注意的部份,那就是這些變數的生命週期。因為這些變數回被放在記憶體中,直到下次比對成功,要注意,是比對成功。所以如果你的程式是這麼寫的話:


my $content = "Taipei Perl Monger";
$content =~ /(Monger$)/;    # $1 現在是 Monger
print $1;
$content = /(perl)/;        # 比對失敗
print $1;                   # 所以還是印出 Monger

當你第一次成功比對之後,Perl會把你所需要的結果放如暫存變數$1中,所以你第一次列印$1時就會看到Perl印出Monger,於是我們繼續進行下一次的比對,這次我們希望比對perl這個字串,並且把比對要的字串同樣的放入$1之中。可惜我們的字串中,並沒有perl這個字串,而且我們也沒有加上修飾符號去進行忽略大小寫的比對,因此這次的比對是失敗的,可是Perl並不會先清空暫存變數$1,因此變數的內容還是我們之前所比對成功的結果,也就是Monger,這從最後印出來的時候就可以看出來了。
比較容易的解決方式就是利用判斷式去根據比對的成功與否決定是否列印,就像這樣:

my $content = "Taipei Perl Monger";
print $1 if ($content =~ /(Monger$)/);    # 因為比對成功,所以會印出Monger
print $1 if ($content = /(perl)/);        # 這裡就不會印出任何結果了

8.6 定位點
要能夠精確的描述正規表示式,還有一項非常重要的工具,就是定位點。其中你可以指定某個樣式必須要被放在句首或是句尾,比如你希望比對某個字串一開始就是"Perl"這個字串。那麼你可以把你的樣式這樣表示:

/^Perl/

其中的^就是表示字串開始的位置,也就是只有在開始的位置比對到這個字串才算成功。 當然,你可以可以使用$來表示字串結束的位置。以這個例子來看:

my $content = "Taipei Perl Monger";
if ($content =~ /Monger$/s) {  		# 以定位字元進行比對
    print "*Match*";      			# 在這裡可以成功比對
}

8.7 比對與替換
就像很多編輯器的功能,我們不只希望可以找到某個字串,還希望可以進行替換的功能。當然正規表示式也有提供類似的功能,甚至更為強大。不過其實整個基礎還是基於比對的原則。也就是必須先比對成功之後才能開始進行替換,所以只要你能了解整個Perl正規表示式的比對原理,接下來要置換就顯得容易多了。現在我們先來看一下在Perl的正規表示式中該怎麼描述正規表示式中的替換。
我們可以使用s///來表示替換,其中第一個部份表示比對的字串,第二個部份則是要進行替換的部份。還是舉個例子來看會清楚一些:

my $content = "I love Java";
print $content if ($content =~ s/Java/Perl/);  # 假如置換成功,則印出替換過的字串

當然,就像我們所說的,置換工作的先決條件是必須完成比對的動作之後才能進行,因此如果我們把剛剛的程式改寫成

my $content = "I love Java";
print $content if ($content =~ s/java/perl/);

那就甚麼事情也不會發生了。當你重新檢查字串$content時,就會發現正如我們所預料的,Perl並沒有對字串進行任何更動。
不過有時候我們會有一些問題,就像這個例子:

my $content = "水果對我們很有幫助,所以應該多吃水果";
print $content if ($content =~ s/水果/零食/);  # 把水果用零食置換

看起來好像很容易,我們把零食取代水果,可是當結果出來時,我們發現了一個問題。Perl的輸出是:「零食對我們很有幫助,所以應該多吃水果」。當然,這跟我們的期待是不同的,因為我們實在想吃零食啊。可是Perl只說了零食對我們有幫助,我們還是得吃水果。
沒錯,我們注意到了,Perl只替換了一次,因為當第一次比對成功之後,Perl就接收到比對成功的訊息,於是就把字串依照我們的想法置換過,接著....收工。好吧,那我們要怎麼讓Perl把整個字串的所有的「水果」都換成「零食」呢?我們可以加上/g這個修飾字元,這是表示全部置換的意思。所以現在應該會是這個樣子:

my $content = "水果對我們很有幫助,所以應該多吃水果";
print $content if ($content =~ s/水果/零食/g);  # 把水果全部換成零食吧

就像我們在比對時用的修飾字元,我們在這裡也可以把那些修飾字元再拿出來使用。就像這樣:

my $content = "I love Perl. I am a perl monger";
print $content if ($content =~ s/perl/Perl/gi);

我們希望不管大小寫,所有字串中的Perl一律改為Perl,所以就可以在樣式的最後面加上/gi兩個修飾字元。而且使用的方式和在進行比對時是相同的方式。

8.8 有趣的字串內交換
這是個有趣的運用,而且使用的機會也相當的多,那就是字串內的交換。這樣聽起來非常難以理解,舉個例子來看看。
我們有一個字串,就像這樣:

$string = "門是開著的,燈是關著的"

看起來真是平淡無奇的一個句子。可是如果我們希望讓門關起來,並且打開燈,我們應該怎麼作呢?
根據我們剛剛學到的替換,這件事情好像很簡單,我們只要把門跟燈互相對調就好,可是應該怎麼作呢?如果我們這麼寫:

$string =~ s/門/燈/;

那整個字串就變成了「燈是開著的,燈是關著的」,那接下來我們要怎麼讓原來「燈」的位置變成「門」呢?所以這種作法似乎行不通,不過既然要交換這兩個字,我們是不是有容易的方法呢?利用暫存變數似乎是個可行的方法,就像這樣:

my $string = "門是開著的,燈是關著的";

print $string if ($string =~ s/(門)(.*)(,)(燈)(.*)/$3$2$1$4/);

看起來好像有點複雜,不過卻非常單純,我們只要注意正規表示式裡面的內容就可以了。在樣式表示裡面,非常簡單,我們要找門,然後接著是「門」和「燈」中間的那一串文字,緊接在後面的就是「燈」,最後的就全部歸在一起。按照這樣分好之後,我們希望如果Perl比對成功,就把每一個部份放在一個暫存變數中。接下來就是進行替換的動作,我們把代表「門」跟「燈」的暫存變數$1及$3進行交換,其餘的部份則維持不變。我們可以看到執行之後的結果就像我們所期待的一樣。
當然,這樣只是最簡單的交換,如果沒有正規表示式,那真的會非常的複雜,不過現在我們還可以作更複雜的交換動作。

8.9 不貪多比對
其實在很多狀況下,我們常常不能預期會比對甚麼樣的內容,就像我們常常會從網路上抓一些資料回來進行比對,這時候我們也許有一些關鍵的比對樣式,但是大多數的內容卻是未知的。因此比對的萬用字元(.)會經常被使用,可是一但使用了萬用字元,就要小心Perl會一路比對下去,一直到不合乎要求為止,就像這樣:

first
second
third

這是非常常見的HTML語法,假設我們希望找到其中的三個元素,所以就必須過濾掉那些HTML標籤。如果你沒注意,也許會寫成:

my $string = "
first
second
third
"; if ($string =~ m|
(.+)<\/td><\/tr>|) { print "$1";}
可是當你看到執行結果時可能會發現那並不是你要的結果,因為程式印出的$1居然是:

first
second
third

讓我們來檢查一下程式出了甚麼問題。我們的比對樣式中告訴Perl,從
開始比對,然後比對所有字元,一直到遇到
second
third"。可是我們要的卻是「從
開始,遇到
(.+?)<\/td><\/tr>|
如此一來,就符合我們的要求了。

8.10 如果你有疊字
在正規表示式中,有一種比對的技巧稱為回溯參照(backreference)。我們如果可以用個好玩的例子來玩玩回溯參照也是不錯的,比如我們有個常見的句子:「庭院深深深幾許」。如果我希望比對中間三個深,我可以怎麼作呢?當然,直接把「深深深」當作比對的樣式是個方法,不過顯而易見的,這絕對不是個好方法。至少你總不希望看到有人把程式寫成這樣吧:

my $string = "庭院深深深幾許";
print $string if ($string =~ /深深深/);  	# 這樣寫程式好像真的很糟

這時候回溯參照就是一個很好玩的東西,我們先把剛剛的程式改成這樣試試:

my $string = "庭院深深深幾許";
print $string if ($string =~ /(深)\1\1/);

你應該發現了,我們把「深」這個字先放到暫存變數中,然後告訴Perl:如果有東西長的跟我們要比對的那個變數裡的東西一樣的話,那麼就用來繼續比對吧。可是這時候你卻不能使用暫存變數$1,因為暫存變數是在比對完成之後才會被指定的,而回溯參照則是在比對的期間發生的狀況。剛剛那個例子雖然可以看出回溯參照的用途,可是要了解他的有用之處,我們似乎該來看看其他的例子:

my $string = "/Chinese/中文/";
if ($string =~ m|([\/\|'"])(.*?)\1(.*?)\1|) {  	# 這時候我們有一堆字符集合
    print "我們希望用 $3 來替換 $2 \n";
}

看出有趣的地方了嗎?我們在字符集合裡面用了一堆符號,因為不論在字符集合裡的那一個符號都可以算是正確比對。但是我們在後面卻不能照舊的使用[\/\|'"]來進行比對,為甚麼呢?你不妨實驗一下這個例子:

my $string = "/Chinese|中文'";
if ($string =~ m|([\/\|'"])(.*?)[\/\|'"](.*?)[\/\|'"]|) {
    print "我們希望用 $3 來替換 $2 \n";
}

很幸運的,我們在這裡還是比對成功。為甚麼呢?我們來檢查一下這次的比對過程:首先我們有一個字符集合,其中包括了/|'"四種字符,而這次我們的字串中一開始就出現了/字符,正好符合我們的需求。接下來我們要拿下其他所有的字元,一直到另一個相同的字符集合,不過我們這次拿到了卻是|字符,最後我們拿到了一個單引號(')。顯然不單是方便,因為沒有使用回溯參照的狀況下,我們拿到了錯誤的結果。
那我們回過頭來檢查上一個例子就會清楚許多了,我們一開始還是一個字符集合,而且我們也比對到了/字符。接下來我們要找到跟剛剛比對到相同的內容(也就是要找到下一個/),然後還要再找最後一次完全相同的比對內容。我們經常會遇到單引號(')或雙引號(")必須成對出現,而利用回溯參照就可以很容易的達成這樣的要求。

8.11 比對樣式群組

我們剛剛說了關於回溯參照的用法,不過如果我們的比對並沒有那麼複雜,是不是也有簡單的方式來進行呢?我們都知道很多人喜歡用blahblah來進行沒有什麼意義的留言,於是我們想把這些東西刪除,可是他們可能是寫"blahblah"或是"blahblahblah"等等。這時候使用回溯參照可能會寫成這樣:

my $string = "blahblahblah means nothing";

if ($string =~ s/(blah)\1*//) {
    print "$string";
}

當然這樣的寫法並沒有錯,只是好像看起來比較礙眼罷了,因為我們其實可以用更簡單的方法來表達我們想要的東西,那就是比對樣式群組。這是小括號(())的另外一個用途,所以我們只要把剛剛的比對樣式改成這樣:/(blah)+/就可以了。這樣一來,Perl就會每次比對(blah)這個群組,然後找尋合乎要求的群組,而不是單一字元(除非你想把某一個字元當群組,只是我們並不覺得這樣的方式會有特殊的需求)。而當我們設定好某個群組之後,他的操作方式就跟平常在寫比對樣式沒什麼兩樣了,我們就可以利用/(blah)+/找出(blah)這個群組出現超過一次的字串。如果你覺得不過癮,/(blah){4,6}/來確定只有blah出現四到六次才算比對成功也是可以的。

8.12 比對樣式的控制
一開始使用正規表示式的人總有一個疑問,為甚麼要寫出正確比對的樣式這麼不容易。而比對錯誤的主要原因通常在於得到不必要的資料,也就是比對樣式符合了過多的文字,當然,還有可能是比對了不被我們期待的文字。就像我們有這樣的一堆字串:

I am a perl monger
I am a perl killer
it is so popular.

如果你的比對樣式是/p.*r/,那麼你會比對成功:

perl monger
perl killer
popular

可是這跟我們的需求好像差距太大,於是你希望用這樣的樣式來進行比對:/p\w+\s\w+r/,那你也還會得到

perl monger
perl killer

這兩種結果。所以怎麼在所取得的資訊中寫出最能夠精確比對的樣式確實是非常重要,也需要一些經驗的。

習題:
1. 延續第七章的第一題,比對出perl在字串結尾的成功結果。
2. 繼續比對使用者輸入的字串,並且確定是否有輸入數字。
3. 利用回溯參照,找出使用者輸入中,引號內(雙引號或單引號)的字串。
4. 找出使用者輸入的第一個由p開頭,l結尾的英文字。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值