本文關注的是正則表達式,只是由於Perl對正則表達式的支持優於其餘語言,因此選用Perl,請不要過多的關心Perl是怎麼回事,必要的前置知識會在這裏說起。下面開始咱們的正則之旅。在本文,會使用 · 來代替正則表達式中出現的空格。git
下面是一段簡單的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中正則表達式的使用。
在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]*
之間在匹配的結果有什麼差異?