精通正則表達式:第二章(1)

精通正則表達式:第二章


本文關注的是正則表達式,只是由於Perl對正則表達式的支持優於其餘語言,因此選用Perl,請不要過多的關心Perl是怎麼回事,必要的前置知識會在這裏說起。下面開始咱們的正則之旅。在本文,會使用 · 來代替正則表達式中出現的空格git


1、簡單易懂的Perl魔法

下面是一段簡單的Perl示例程序,功能是將華氏溫度轉換爲攝氏溫度。正則表達式

$celsius = 30;
    $fahrenheit = ($celsius * 9 / 5) + 32;    #計算華氏溫度
    print "$celsius C is $fahrenheit F.\n"    #輸出兩種溫度

其結果爲:shell

30 C is 86 F.

從這段程序中,咱們會發現Perl的幾個特色:oop

  • 普通的變量(如$celsius)以$開頭,能夠保存數值或者字符串。測試

  • \#表明着註釋的開始code

  • 變量能夠出如今引號包圍的字符串中,最後會被其實際值替代。口怕!對象

同時,Perl也提供了流程控制語句,如while:字符串

$celsius = 20;
    while ($celsius <= 45)
    {
        $fahrenheit = ($celsius * 9 / 5) + 32;
        print "$celsius C is $ fahrenheit F.\n";
        $celsius = $celsius + 5;
    }

運行結果以下:input

20 C is 68 F.
25 C is 77 F.
30 C is 86 F.
35 C is 95 F.
40 C is 104 F.
45 C is 113 F.

當條件爲真的時候,while循環控制的部分就會重複執行,直到條件爲假。若是在終端中運行,則就像下面這樣:it

/~> perl -w CelToFah.pl

這裏的-w參數不是必須的,可是加上參數之後,Perl會在可疑的地方報錯。這算是一種良好的習慣罷了。因爲Perl不是本文的重點,因此介紹就到這裏爲止,下面是Perl中正則表達式的使用。


2、匹配文本

在Perl中,最簡單的正則表達式使用方法就是:檢查變量中的文本是否能匹配指定正則表達式。實例片斷以下:

if ($reply =~ m/^[0-9]+$/){
            print "only digits\n";
        } else {
            print "not only digits\n";
    }

如你所見,第一行的表達式很有魔法風範:正則表達式是^[0-9]+$m/.../則通知Perl要對正則表達式進行什麼操做,m意味着嘗試進行正則表達式匹配,而斜槓則用來標記界限;=~則用來鏈接對象字符串和正則表達式。

須要注意的是,=~===三者請勿混淆。=~用於正則表達式,=用於變量賦值, 而==則用於測試數值是否相等。字符串是否相等,使用的是eq。在這裏,表達式

$reply =~ m/^[0-9]+$/

的返回值取決於變量reply。若是其內容能匹配正則表達式m/^[0-9]+$/,則會返回真。而兩端的^$則保證其只包含數字。接下來則是兩個例子的結合。

首先,會提示用戶輸入一個值,接受這個輸入並用正則表達式去驗證:若是輸入的是數值,則計算相應的華氏溫度;不然報錯。實例以下:

print "Enter a temperature in Celsius:\n";
    $celsius = <STDIN>;     #從用戶處接受一個輸入
    chomp($celsius);        #去掉換行符
    
    if ( $celsius =~ m/^[0-9]+$/) {
        $fahrenheit = ($celsius * 9 / 5) + 32;      #計算華氏溫度
        print "$celsius C is $fahrenheit F\n";
    } else {
        print "Expecting a number, so I don't understand \"$celsius\".\n";

}

字符裏面的轉義就再也不贅述了。要注意的是,Perl中,字符串和正則表達式的區別既不明顯,也不重要,這是它和其餘語言的一大區別。運行結果以下:

Enter a temperature in Celsius:
123
123 C is 253.4 F

該版本的Perl浮點數處理的很好……那我就不黑了。

更進一步

咱們能夠拓展這個例子,使它支持小數和負數。計算部分就交給Perl吧。負數就是一個可選的負號,而小數則是可選的小數點和任意數字。因此拓展後的正則表達式是這樣的:

m/^-?[0-9]+(\.[0-9]*)?$/

如今,他就能夠匹配-1九、0.343這類的數字了,可是.9834這種數字仍是沒法匹配,因爲不是什麼大問題,咱們會留到很後面再來處理。

成功匹配的反作用

如今,咱們除了要匹配數字,還要用戶能夠輸入C和F來標識輸入的溫度類型,並進行轉換。
咱們知道,正則表達式能夠捕獲匹配文本,並在可以在正則表達式以外進行引用。而Perl則經過臨時變量$1/$2/$3指向分組內的子表達式匹配的文本。
總之,匹配過程當中,使用/1來匹配的文本;而在匹配事後,用$1指向匹配的文本。爲此,咱們須要修改表達式。首先,忽略並去掉小數部分的匹配,以突出新特色。

m/^([-+]?[0-9]+)([CF])$/

在這個表達式中,使用括號圍住了「有價值」的部分,捕捉事後,咱們能夠決定要使用它們來作什麼。如今,咱們打算實現以前提到的事情:匹配數字,還要用戶能夠輸入C和F來標識輸入的溫度類型,並進行轉換。

print "Enter a temperature in Celsius:\n";
    $input = <STDIN>;     #從用戶處接受一個輸入
    chomp($input);        #去掉換行符
    
    if ( $input =~ m/^([-+]?[0-9]+)([CF])$/) {
        #程序運行到這裏就已經匹配好了。$1保存數字,$2保存符號。
        $InputNum = $1;
        $type = $2;
        
        if ($type eq "C") {
            #輸入爲攝氏溫度,計算華氏溫度
            $celsius = $InputNum;
            $fahrenheit = ($celsius * 9 / 5) + 32;
        } else {
            #不然,應該是"F",那就計算攝氏溫度。
            $fahrenheit = $InputNum;
            $celsius = ($fahrenheit - 32) *5 /9;
        }
        #如今獲得兩個溫度值,顯示結果,並使用格式化字符串。
        printf "%.2f C is %.2f F.\n", $celsius, $fahrenheit ;
    } else{
        #若是一開始沒有匹配,則報錯。
        print "Expecting a number followed by \"C\" or \"F\",\n";
        print "So I don't understand \"$input\".\n";
    }

結果以下:

PS E:\LearnPerl> perl -w .\REdigits1.pl
Enter a temperature in Celsius:
22F
-5.56 C is 22.00 F.
PS E:\LearnPerl> perl -w .\REdigits1.pl
Enter a temperature in Celsius:
39C
39.00 C is 102.20 F.
PS E:\LearnPerl> perl -w .\REdigits1.pl
Enter a temperature in Celsius:
oops
Expecting a number followed by "C" or "F",
So I don't understand "oops".

但這裏離成功還有必定距離,好比:

  • 沒法接受浮點數

  • 不能允許小寫的c和f

  • 不能接受數字和字母之間的空格

爲了遇上這些距離,咱們還有幾件事情要作。首先,咱們向正則表達式添加小數部分的匹配。修改以下:
m/^([-+]?[0-9]+(\.[0-9]*)?)([CF])$/
這裏,我在小數部分添加了一個括號。括號自己雖然沒有被咱們使用,可是確實影響了引用捕獲文本的變量。如今,結果變成了下面這樣:

Enter a temperature in Celsius:
11.2F
type is .2
InputNum is 11.2

能夠明顯的看到,$1匹配的整個數字,也就是外圍的第一個括號分組([-+]?[0-9]+(\.[0-9]*)?);而$2則匹配第一個括號分組嵌套(\.[0-9]*);$3則是原來的變量$2。這樣就能夠明白,分組的序號由分組的開括號(在 表達式中的順序有關(從左到右)。
咱們如今能夠將$type變量的賦值改成$3,或者,使用非捕獲型括號

可使用(?:...)來表示只分組,不捕獲。這樣,一是不會影響捕獲計數,二是能夠提升匹配效率,三是讓代碼更加清晰。可是,若是是隻使用一次的正則,能夠考慮棄之不用。

如今,咱們能夠來處理空格了。咱們可使用·*來表示。再度修改以下:
m/^([-+]?[0-9]+(?:\.[0-9]*)?) *([CF])$/
有人注意到哪裏修改了嗎?嗯……這樣確實很難注意到這邊有一個空格。與此同時,若是輸入的是製表符(天知道爲何會輸入進來),那就匹配不到了。因此,咱們可使用元字符\s來匹配空白字符,三度修改以下:
m/^([-+]?[0-9]+(?:\.[0-9]*)?)\s*([CF])$/
好了,如今只剩下小寫字母的問題了。咱們可使用一個修飾符(modifier)。
結果變成這樣:m/^([-+]?[0-9]+(?:\.[0-9]*)?)\s*([CF])$/i`這個修飾符只是Perl中的用法,其餘語言有不一樣的實現方式,如Python使用的在編譯的時候指定。
如今,大功告成,來測試一下:

Enter a temperature in Celsius:
33.98 c
1.10 C is 33.98 F.

結果不盡如人意……嗯,再修改一下便可。
最終版本就是這樣子的:

print "Enter a temperature in Celsius:\n";
    $input = <STDIN>;     #從用戶處接受一個輸入
    chomp($input);        #去掉換行符
    
    if ( $input =~ m/^([-+]?[0-9]+(?:\.[0-9]*)?)\s*([CF])$/i) {
        #程序運行到這裏就已經匹配好了。$1保存數字,$2保存符號。
        $InputNum = $1;
        $type = $2;
        
        if ($type =~ m/c/i) {
            #輸入爲攝氏溫度,計算華氏溫度
            $celsius = $InputNum;
            $fahrenheit = ($celsius * 9 / 5) + 32;
        } else {
            #不然,應該是"F",那就計算攝氏溫度。
            $fahrenheit = $InputNum;
            $celsius = ($fahrenheit - 32) *5 /9;
        }
        #如今獲得兩個溫度值,顯式結果。
        printf "%.2f C is %.2f F.\n", $celsius, $fahrenheit ;
    } else{
        #若是一開始沒有匹配,則報錯。
        print "Expecting a number followed by \"C\" or \"F\",\n";
        print "So I don't understand \"$input\".\n";
    }

到這裏就先休息下吧。順便來道題目思考思考:

(·*|\t*)[·\t]*之間在匹配的結果有什麼差異?

相關文章
相關標籤/搜索