通過本文的學習,您可以增加一些有用的設計實際正則表達式 (regexp) 的技能。構建正則表達式是任何管理員日常工作中的一部分。為了構造返回所需條件的成功正則表達式,需要學習以模式匹配的角度進行思考,而這種技能需要花大量的時間進行練習。
UNIX® 管理員每天都需要構建和使用正則表達式 (regexp) 進行文本模式匹配。大多數語言都支持正則表達式的某種實現。有的應用程序(如 EMACS)具有正則表達式搜索功能,并且您可以通過各種命令行工具使用正則表達式。無論什么應用程序,構建正確的正則表達式的關鍵之處在于,識別僅滿足需要匹配的數據的模式,以便在輸入中排除其他不必要的內容。
出于這個目的,本文將逐步介紹幾種正則表達式模式構建技巧,并介紹它們如何幫助您完成各種常規任務。
除非特別說明,否則本文中使用的示例都是擴展可移植操作系統接口(擴展 POSIX)的正則表達式。如果通過命令行(如使用 egrep
實用工具)使用它們,您應該根據需要引用各種正則表達式。請記住,不同的正則表達式實現之間存在一些區別,您可能不得不適應所使用的特定的工具、應用程序或語言中的具體實現。
^
元字符匹配行首,而 $
匹配行尾,如果將它們組合在一起(如 ^$
),它們將匹配空行。(這個表達式的鏡像,即 $^
,是不可能匹配成功的,它將永遠 都無法匹配到有效行。)這個基本的正則表達式是許多復雜正則表達式的基礎,如果您還不習慣使用這個基本的正則表達式,那么您應該逐步養成使用它的習慣。使用它來構建匹配整行內容 的模式。
在用戶字典文件 (/usr/dict/words) 中搜索是一個很好的基本模式。(有些版本的 UNIX 將用戶字典放在 /usr/share/dict/words 中。)
例如,假設您忘記了如何拼寫單詞 fuchsia。其中是否包含 sh 或 cs 呢?您所知道的只是,它以 fu 開頭并以 ia 結尾。
嘗試使用這個模式進行搜索:
$ egrep -i '^fu.*ia$' /usr/dict/words |
-i
標志表示在搜索過程中不區分大小寫。在這個示例中,因為 fuchsia 拼寫正確,所以在返回的單詞中包括這個單詞。
使用大括號元字符 ({
}
) 指定前面的正則表達式匹配多少次,如表 1 所示。當您將它們添加到剛才介紹的整行搜索中時,您可以指定行的長度。
示例 | 描述 |
---|---|
{X} |
這個字符對前面的正則表達式匹配 X 次。 |
{X,} |
這個字符對前面的正則表達式匹配 X 或更多 次。 |
{X,Y} |
這個字符對前面的正則表達式匹配至少 X 而不超過 Y 次。 |
并不是所有擴展正則表達式的實現都支持大括號。此外,根據具體的實現,您可能需要先使用反斜杠對其進行轉義。
您可以使用這個正則表達式得到字典中以單詞長度為順序的報告。所獲得結果的數目取決于本地系統的字典文件中單詞的數目,然而,它應該與清單 1 所示類似。在這個示例中,最常見的單詞長度是 9 個字母,該字典中有 32,380 個匹配單詞。該字典中不包括 25 個字母或更長的單詞,并且最長的單詞并不是您認為的 21 個字母長的 disestablishmentarian(有 81 個同樣長度的單詞,包括 superincomprehensible 和 phoneticohieroglyphic),這個 UNIX 字典中最長的單詞有 5 個,包括 pathologicopsychological。
$ for i in `seq 1 32` > { > echo "There are" `egrep '^.{'$i'}$' /usr/dict/words | wc -l` "$i-letter words in the dictionary." > } There are 52 1-letter words in the dictionary. There are 155 2-letter words in the dictionary. There are 1351 3-letter words in the dictionary. There are 5110 4-letter words in the dictionary. There are 9987 5-letter words in the dictionary. There are 17477 6-letter words in the dictionary. There are 23734 7-letter words in the dictionary. There are 29926 8-letter words in the dictionary. There are 32380 9-letter words in the dictionary. There are 30867 10-letter words in the dictionary. There are 26011 11-letter words in the dictionary. There are 20460 12-letter words in the dictionary. There are 14938 13-letter words in the dictionary. There are 9762 14-letter words in the dictionary. There are 5924 15-letter words in the dictionary. There are 3377 16-letter words in the dictionary. There are 1813 17-letter words in the dictionary. There are 842 18-letter words in the dictionary. There are 428 19-letter words in the dictionary. There are 198 20-letter words in the dictionary. There are 82 21-letter words in the dictionary. There are 41 22-letter words in the dictionary. There are 17 23-letter words in the dictionary. There are 5 24-letter words in the dictionary. There are 0 25-letter words in the dictionary. There are 0 26-letter words in the dictionary. There are 0 27-letter words in the dictionary. There are 0 28-letter words in the dictionary. There are 0 29-letter words in the dictionary. There are 0 30-letter words in the dictionary. There are 0 31-letter words in the dictionary. There are 0 32-letter words in the dictionary. $ |
圍繞字符 \<
和 \>
是非常有用的模式構造器:它們將要匹配的整個單詞 括起來,這表示,它們不會匹配帶括號的模式,除非該模式本身就是一個單詞。單詞 定義為兩側由非單詞字符描述的、任意數目組成單詞的字符(數字、字母和下劃線字符)。非單詞字符包括下面的所有字符:
這些圍繞字符可以節省大量的時間,但是它們常常沒有被充分地利用,可能是因為并非所有的正則表達式實現都支持它們。如果您的正則表達式實現支持它們,那么您應該逐步養成使用它們的習慣。
將需要單獨匹配的單詞括起來,如下所示:
\<system\> |
這個示例中的正則表達式不會匹配單詞 ecosystem、systemic 或 system/70,也不會匹配模式 system
出現在行中任意位置的那些行,它將僅僅 匹配 system 作為獨立的單詞出現的那些行。
圍繞字符與圓括號中的分組結合在一起,可以用來匹配部分 單詞。
要匹配包含以 pre 開頭 的單詞的那些行,可以使用:
\<\(pre\).*\> |
前面的示例將匹配包含單詞 preface 和 preposterous 的行,但不會匹配 spread 或 Dupre。
這里介紹一種使用單詞圍繞字符匹配重復單詞的快速方法,重復單詞表示一個單詞在空格之后再次出現。您還可以使用逆向引用,這是大多數流行的正則表達式實現中的一種遞歸特性,它可以匹配模式本身的某一部分。(將模式中需要引用的部分使用圓括號括起來,然后使用反斜杠加上需要進行引用的圍繞字符編號來調用逆向引用:1
表示第一個圓括號分組,2
表示第二個圓括號分組,依此類推。)
要查找重復的單詞,搜索在任意數目的空格之后再次出現該單詞的情況,可以通過對第一個使用圓括號的部分進行逆向引用來實現:
(\<.*\>)( )+\1 |
這個示例匹配縮寫形式和任何類型的單詞,但是它不會匹配由標點符號分隔的重復單詞,如 It's been a long, long time。
要匹配所有的重復單詞,包括由空格和 任意標點符號分隔的重復單詞,可以使用下面的表達式:
(\<.*\>).?( )+\1 |
如果需要對這些正則表達式使用 grep,則務必使用 -i
標志,以便在搜索中不區分大小寫。
讓我們再來看另外一類常見的問題:時間和日期。這里介紹了一些設計匹配正確模式的正則表達式所需要考慮的事項。
您無法搜索任何兩位的數字來匹配分鐘和秒,因為它們僅僅是從 0 到 59,要匹配它們,您需要使用方括號將表示十位和個位的范圍括起來:
(([0-1]?[0-9])|([2][0-3])):([0-5][0-9])(:[0-5][0-9])? |
([^0-9])([0-1]?[0-9]){1}(((:([0-5]){1}([0-9]){1}){1,2})|(( )?([AP]M)|([ap]m)))? |
如果在上一個示例中沒有開始的否定語句,它將匹配不帶冒號的時間,這將取決于輸入數據,可能會匹配中波廣播電臺(在美國稱為調幅 AM 電臺),如 1450 AM。
匹配 12 個月中的任何月份需要一個使用 |
操作符進行分隔的列表,但有時會使用不同的方式對日期進行縮寫:
Jan(uary)?|Feb(uary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?| Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)? |
Jan(uary| |\.)|Feb(uary| |\.)|Mar(ch| |\.)|Apr(il| |\.)|May( |\.)|Jun(e| |\.)|Jul(y| | .)|Aug(ust| |\.)|Sep(tember| |\.)|Oct(ober| |\.)|Nov(ember| |\.)|Dec(ember| |\.) |
請注意,在上面的這兩個示例中,May 是一個特殊的例外。在所有的月份中,它是唯一的完整拼寫與三字母縮寫相同的月份,所以成功的匹配必須包含這兩種變形中的任何一種 作為其縮寫,因此像“Mayflower”這樣的單詞不會導致誤報。
當匹配模式前面的字符不是空格或行首時,這些示例還是會失?。ǚ祷卣`報的結果)。這不太可能會出現在英語散文中,但是可能出現在程序源代碼中,因為其中可能使用了像 NumOct
這樣的變量名。
要修復這些問題,可以執行下面的操作:
(^| )(Jan(uary| |\.)|Feb(uary| |\.)|Mar(ch| |\.)|Apr(il| |\.)| May( |\.)|Jun(e| |\.)|Jul(y| |\.)|Aug(ust| |\.)|Sep(tember| | \.)|Oct(ober| |\.)|Nov(ember| |\.)|Dec(ember| |\.)) |
([^A-Za-z0-9])(Jan(uary| |\.)|Feb(uary| |\.)|Mar(ch| |\.)| Apr(il| |\.)|May( |\.)|Jun(e| |\.)|Jul(y| |\.)|Aug(ust| |\.)| Sep(tember| |\.)|Oct(ober| |\.)|Nov(ember| |\.)|Dec(ember| |\.)) |
但是仍然存在潛在的問題,對于搜索整篇英文散文,這些示例并不可靠,因為它們可能返回錯誤的匹配結果,如“Janelle”或“Augury”這樣的單詞。要修復這個問題,您必須使用單詞圍繞字符將每個月份括起來。
本文開頭提到,正確的正則表達式應該僅返回需要匹配的數據,以便在輸入中排除其他不必要的內容。這種措詞是經過仔細考慮的,因為對于構建正則表達式來說,這與上下文有關。對于有些情況,前面的示例非常適合,無需添加額外的單詞圍繞字符。在其他的情況下,可以對其進行相當程度的簡化,例如,如果您正在搜索僅包含大寫的日期數值數據的日志文件,那么只需要使用像 [A-S]
這樣的正則表達式來匹配包含月份名稱的行。
您可以結合一些表 1 所示的數量匹配來匹配日期。
要匹配“month, day, years”,可以使用下面的正則表達式(因為撇號字符是該正則表達式的一部分,所以必須使用雙引號將它括起來,如下所示):
"[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2})" |
這個正則表達式匹配 9 種不同的日期格式:
MONTH [D]D, YY
MONTH [D]D, 'YY
MONTH [D]D, YYYY
MON. [D]D, YY
MON. [D]D, 'YY
MON. [D]D, YYYY
MON [D]D, YY
MON [D]D, 'YY
MON [D]D, YYYY
這個正則表達式的誤報包括“Order 99, 99”,要消除這些誤報,可以將這個正則表達式與用于月份的正則表達式結合起來,如上所述,以便能夠僅匹配實際的月份名稱。另外,更改數值范圍以避免錯誤的匹配,并且通過使逗號成為可選項,重復了 18 種可能的格式。
這將得到一個很長的正則表達式。嘗試下面的表達式:
"([^A-Za-z0-9])(Jan(uary| |\.)|Feb(uary| |\.)|Mar(ch| |\.)| Apr(il| |\.)|May( |\.)|Jun(e| |\.)|Jul(y| |\.)|Aug(ust| |\.)| Sep(tember| |\.)|Oct(ober| |\.)|Nov(ember| |\.)| Dec(ember| |\.)) [0-3]?[0-9]{1}(,)? ([0-9]{4}|'?[0-9]{2})" |
同樣,根據您的需要仔細設計正則表達式。匹配模式通常比較容易,這是因為它存在于特定輸入的上下文中,而不是因為它可能獨立于數據集而存在。后代人將會發現,前面那個很長的正則表達式中仍然存在 Y10K 錯誤,因為它能匹配的最大可能的年份為 9999。
正如您在前幾個示例中看到的,使用方括號中的范圍可以很好地匹配數值。
要匹配任意長度的整數,可以在數值范圍后面加上 +
;要包括負值,可以在它的前面加上可選的負號(連字號)匹配:
-?[0-9]+ |
前面的例子可以匹配 0,因為 0 是指定范圍中可選的字符。
對于數值匹配,使用圓括號將某些部分括起來也非常有效。要匹配任意的十進制數值,可以使用包含小數點加上一個或多個數值的可選圍繞字符,以此對前面的正則表達式進行擴展:
-?[0-9]+(\.[0-9]+)? |
可以使用方括號指定十進制數值的小數位數。例如,要匹配小數位數為 5 或更多小數位數的正數值,可以使用下面的表達式:
[^-][0-9]+\.([0-9]){5,} |
范圍加上使用括號括起來的元字符,在查找符合任何特定格式的數值時非常有用。將前面介紹的一些技術結合起來,可以構建匹配各種數據的正則表達式:
((\([2-9][0-9]{2}\))?\ ?|[2-9][0-9]{2}(?:\-?|\ ?))[2-9][0-9]{2}[- ]?[0-9]{4} |
這個正則表達式可以匹配美國 15 種格式的電話號碼:
(NPA) PRE-SUFF
(NPA) PRE SUFF
(NPA) PRESUFF
(NPA)PRE-SUFF
(NPA)PRE SUFF
(NPA)PRESUFF
NPA PRE-SUFF
NPA PRE SUFF
NPA PRESUFF
NPAPRE-SUFF
NPAPRE SUFF
NPAPRESUFF
PRE-SUFF
PRE SUFF
PRESUFF
它還可以匹配美國免費 WATS 號碼,盡管 1-800 的“1-”前綴或其他的免費號碼不是匹配的一部分,但它本身可以匹配 10 位的數值。對于以 1 或 1+ 開頭的美國號碼和任意數目的空格,也完全一樣,長途電話撥號前綴本身無法匹配,但是只要它后面跟著實際的號碼,這個正則表達式就能夠將其找出來。
\<[^@]+\>@[a-zA-Z_\.]+?\.[a-zA-Z]{2,3} |
(((http(s)?|ftp|telnet|news)://|mailto:)[^\(\)[:space:]]+) |
這個表達式可以正常運行,但是匹配 URL 并不像您想象的那么簡單。匹配任何可能的 URL 的正則表達式,如 RFC 1738 中的定義,發表在“Regexp for URLs”(請參見參考資料部分)一文中,它非常巨大并且看起來令人生畏?,F在應該將它合并為一個 [:url:]
類(如果有用于處理類似數據種類的各種新的類,如 [:email:]
,那就好了)。
本文涉及到一些用于編寫正則表達式的模式構建技術,以及如何使用它們來完成管理員時常碰到的特定類型數據匹配的工作。在此過程中,向您介紹了大量有價值的實際正則表達式,您可以將它們添加到自己的管理工具庫中。