(021) Linux之正则表达式
十年運維系列之基礎篇 - Linux
作者:曾林?
聯系:1494445739@qq.com
網站:www.jplatformx.com
版權:文章未經同意請勿轉載
一、引言
? ? ? 簡單地說,正則表達式是一種符號表示法,用來識別文本模式。在某種程度上,他們類似于匹配文件和路徑名時使用的shell通配符,但其用途更廣泛。許多命令行工具和大多數編程語言都支持正則表達式,以此來解決文本操作方面的問題。然而,在不同的工具,以及不同的編程語言之間,正則表達式都會略有不同,這讓事情進一步麻煩起來。方便起見,我們將正則表達式的討論限定在POSIX標準中(它涵蓋了大多數命令行工具)。
?
二、grep——文本搜索
? ? ? 我們用來處理正則表達式的主要程序是grep。grep的名字源于“global regular expression print”,由此可以看到,grep與正則表達式有關。實際上,grep搜索文本文件中與指定正則表達式匹配的行,并將結果送至標準輸出。
? ? ? grep按照如下方式接受選項和參數。?
? ? ? shell> grep [options] regex [file...]
? ? ? 其中regex代表的就是某個正則表達式。下表列出了grep常用的選項。
| 選項 | 功能描述 |
| -i | 忽略大小寫。不區分大寫和小寫字符,也可以用--ignore-case指定 |
| -v | 不匹配。正常情況下,grep會輸出匹配行,而該選項可使grep輸出不包含匹配項的所有行。也可以用--invert-match指定 |
| -c | 輸出匹配項數目(如果有-v選項,那就輸出不匹配項目的數目)而不是直接輸出匹配行本身。也可以用--count指定。 |
| -n | 在每個匹配行前面加上該行在文件內的行號。也可以用--line-number指定 |
| -l | 輸出匹配項文件名而不是直接輸出匹配行自身。也可以用--files-with-matches指定 |
| -L | 與-l選項類似,但輸出的是不包含匹配項的文件名。也可以用--files-without-match指定 |
| -h | 進行多文件搜索時,抑制文件名輸出。也可以用--no-filename指定 |
?
三、元字符和文字
? ? ? 雖然看起來不是很明顯,但grep搜索一直都在使用正則表達式,盡管那些例子都十分簡單。正則表達式bzip用于匹配文本中至少包含4個字符、存在連續的按b、z、i、p順序組成的字符串的行。字符串bzip中的字符都是文字字符,即它們只能與自身進行匹配。除了文字字符,正則表達式還可以包含用于指定更為復雜的匹配的元字符。正則表達式的元字符包括以下字符:
? ? ? ^ $ . [ ] { } - ? * + ( ) | \
? ? ? 其他所有字符則被當做文字字符,但是在極少數的情況下,反斜杠字符用來創建元序列,以及用來對元字符進行轉義,使其成文文字字符。
? ? ? 注: 可以看到,當shell在執行擴展時,許多正則表達式的元字符在shell中具有特殊的意義。所以,在命令行中輸入包含元字符的正則表達式時,應把這些元字符用引號括起來以避免不必要的shell擴展。
?
四、任意字符
? ? ? 接下來討論的第一個元字符是“點”字符或者句點字符,該字符用于匹配任意字符。如果將其加入到某個正則表達式中,它將會在對應位置匹配任意字符。舉例如下圖:
? ? ? 上述命令行,搜索到了所有匹配正則表達式.zip的命令行。但其輸出結果有一些有趣的地方,比如說輸出中并沒有包含zip程序,這是因為正則表達式中的“.”元字符將匹配長度增加到了4個字符。而“zip”只包含了三個字符,所以不匹配。同樣,如果列表中某個文件包含了文件擴展名“.zip”,那么該文件也會被認為是匹配文件,因為文件擴展名中的“.”符號也被當做任意字符處理了。
?
五、錨
? ? ? 插入符(^)和美元符號($)在正則表達式里被當做錨,也就是說正則表達式只與行的開頭(^)或是末尾($)的內容進行匹配比較。比如下圖中的例子:
? ? ? 上例中搜索的是行開頭、行末尾都有字符串“zip”(例如:zip自成一行)的文件。請注意:正則表達式“^$”(行開頭和行末尾之間沒有字符)將會匹配空行。
?
六、中括號表達式和字符類
? ? ? 中括號除了可以用于匹配正則表達式中給定位置的任意字符外,還可以用于匹配指定字符集中的單個字符。借助于中括號,我們可以指定要匹配的字符集(也包括那些可能會被解釋為元字符的字符)。如下命令行則利用了一個兩個字母組成的字符集,用于匹配包含bzip和gzip字符串的文本行。如下圖所示:
? ? ? 一個字符集可以包含任意數目的字符,并且當元字符放置到中括號中時,會失去它們的特殊含義。然而,在兩種情況下,則會在中括號中使用元字符,并且會有不同的含義。第一個就是插入符(^),它在中括號內使用表示否定;另外一個是連字符(-),它表示字符范圍。
? ? ? 如果中括號內的第一個字符是插入符(^),那么剩下的字符則被當作不應該在指定位置出現的字符集。作為演示,下圖展示了相關的例子:
? ? ? 通過使用否定操作,我們可以得到那些包含zip字符串但zip前面既不是b也不是g的所有程序。請注意,此時zip命令仍然沒有出現在結果列表中,由此可見否定,字符集仍然需要在指定位置有對應字符,只不過這個字符不是否定字符集中的成員而已。
? ? ? 插入字符“^”只有是中括號表達式中的第一個字符時才會被當作否定符,如果不是第一個,“^”將會喪失其特殊含義而成為普通字符。
?
? ? ? 2. 傳統字符范圍
? ? ? 如果我們建立一個正則表達式,用于查找文件名以大寫字母開頭的文件,可以用下面的命令行:
? ? ? shell> grep -h '^[ABCDEFGHIJKLMNOPQRSTUVWXYZ]' dirlist*.txt
? ? ? 這僅僅是將26個大寫英文字母寫入中括號的小事,但是要輸入26個字母實在有點麻煩,我們可以使用范圍符號來簡化,如下的命令行語句:
? ? ? shell> grep -h '^[A-Z]' dirlist*.txt
?
? ? ? 3. POSIX字符類
? ? ? 首先需要聲明地是,這里所指的POSIX字符類并不是針對grep正則表達式的,而是針對shell路徑名的擴展。
? ? ? 傳統的字符范圍表示方法是很容易理解的,而且能夠有效、快速地指定字符集。但不足之處在于,它并不是所有的情況都適合。雖然到目前為止,在使用grep程序時還沒有遇到任何問題,但是在其他程序中則可能會遇到問題。如下圖所示:
? ? ? Linux發行版本不同,上述命令行得到的結果可能會有不同,甚至有可能是空列表。本例中的列表來自于CentOS系統。該命令行得到了預期的效果——只有以大寫字母開頭的文件列表。但是,如果我們使用下面的命令行,便會得到完全不同的結果。如下圖所示:
? ? ? 為什么會出現這樣的差異呢?下面簡單介紹一下:
? ? ? 在UNIX開發初期,它只識別ASCII字符,而正是這一特性導致了上面的差異。在ASCII碼中,前32個字符(第0~31個字符)都是控制字符(像Tab鍵、空格鍵以及Enter鍵),后32個字符(第32~63)包含打印字符,包括大多數的標點符號以及數字0~9,接下來的32個(64~95)包含大寫字母和一些標點符號,最后的32個字母(第96~127)則包含了小寫字母以及更多的標點符號?;谶@樣的安排,使用ASCII的系統使用了下面的這種排序:
? ? ? ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
? ? ? 這與通常的字典排序不一樣,字典中字母的排序表通常如下:
? ? ?aBbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ
? ? ? 隨著Unix在美國以外的國家普及,人們越來越希望計算機能支持美式英語中找不到的字符。于是ASCII碼字符表也得以擴展,開始使用8位二進制來表示,這就是增加了第128~255個字符,兼容了更多的語言。為了支持這樣的功能,POSIX標準引入了域(locale)的概念,它通過不停調整以選擇特定的位置所需要的字符集。我們可以使用下面的命令行查看語言設置。? ? ??
? ? ? 有了這個設置,POSIX兼容的應用程序使用的便是字典中的字母排列順序,而不是用ASCII嗎中的字符排列順序。這樣,便解釋了上面命令行的詭異行為。A~Z的字符范圍,用字典中的順序詮釋時,包括了字母表中除了小寫字母a的所有字母,因此使用命令行ls /usr/sbin/[A-Z]*才會出現全然不同的結果。
? ? ? 為了解決這一問題,POSIX標準包含了許多標準字符類,這些字符類提供了一些有用的字符范圍。
| 字符類 | 描述 |
| [:alnum:] | 字母字符和數字字符;在ASCII碼中,與[A-Za-z0-9]等效 |
| [:word:] | 基本與[:alnum:]一樣,只是多了一個下劃線字符(_) |
| [:alpha:] | 字母字符,在ASCII碼中,等效于[a-zA-Z] |
| [:blank:] | 包括空格和制表符 |
| [:cntrl:] | ASCII控制碼;包括ASCII字符0~31和127 |
| [:digit:] | 數字0~9 |
| [:graph:] | 可見字符;在ASCII碼中,包括數字33~126 |
| [:lower:] | 小寫字母 |
| [:punct:] | 標點符合字符;在ASCII中,與[-!"#%&'()*+,./:;<=>?@[\\\]_`{|}~]等效 |
| [:print:] | 可打印字符;包括[:graph:]中的所有字符再加上空格字符 |
| [:space:] | 空白字符如空格符、制表符、回車符、換行符、垂直制表符以及換頁符。在ASCII中,等效為[\t\r\n\v\f] |
| [:upper:] | 大寫字母 |
| [:xdigit:] | 用于表示十六進制的字符;在ASCII中,與[0-9A-Fa-f]等效 |
? ? ? 當然,即便是有了這么多字符類,仍然沒有比較方便的方法表示部分范圍,如[A-M]。
? ? ? 使用字符類,上面的例子可以改寫成下面的形式:
? ? ? shell> ls /usr/sbin/[[:upper:]]*
? ? ? 然而,請記住,上述并不是一個正則表達式的實例,它其實只是shell路徑名擴展的一個例子。在此處提及這些,主要是因為這兩種用法都支持POSIX字符集。
? ? ? 當然,你可以設置自己的系統采用傳統的ASCII字符順序,方法就是改變LANG環境變量的值。LANG變量包含語言的名稱以及該語言環境中使用的字符集,該參數值在Linux系統選擇安裝語言時就已經設定。將環境設置為使用傳統的UNIX行為,可將LANG變量值設為POSIX:export LANG=POSIX。請注意,這樣的改變將會導致系統使用美式英語(更精確地說,是ASCII碼格式)的字符集,所以在進行此改變之前請三思。
?
七、POSIX基本正則表達式和擴展正則表達式的比較
? ? ? 在讀者正覺得正則表達式已經復雜到不能在復雜時,又會發現POSIX規范將正則表達式的實現方法分為了兩種:基本正則表達式(BRE)和擴展正則表達式(ERE)。到目前為止,我們所討論的正則表達式的所有特性,都得到了兼容POSIX的應用程序的支持,并且都是以BRE的方式實現。grep命令就是這樣的例子。
? ? ? BRE和ERE到底有什么區別?其實僅僅是元字符的不同!在BRE方式中,只承認^、$、.、[、]、*這些是元字符,所有其他的字符都被識別為文字字符。而ERE中,則添加了(、)、{、}、?、+、|等元字符(及其相關功能)。
? ? ? 然后,只有在用反斜杠進行轉義的情況下,字符(、)、{、}才會在BRE被當作元字符來處理,而ERE中,任何元符號前面加上反斜杠反而會使其被當做文字字符來處理。
? ? ? 由于下面要討論的特性是ERE的一部分,所以需要使用不一樣的grep。傳統上,這是由egrep程序來執行的,但是GNU版本的grep可以運用-E選項以支持ERE方式。
? ? ? 什么是POSIX?POSIX也就是Portable Operating System Interface(末尾增加X只是為了更流暢)的縮寫。它是20世紀80年代中期,IEEE開始開發了一套規范UNIX和類UNIX系統工作方式的標準。這些標準,官方名稱為IEEE 1003,定義了應用程序接口(API)、shell以及一些實用程序,它們可以在標準類UNIX系統中找到。該標準由Richard Stallman提議命名為POSIX,后來被IEEE采納。
?
八、或選項
? ? ? 我們將要討論的第一個擴展正則表達式的特性是或選項,它是用于匹配表達式集的工具。中括號表達式可以從指定字符集中匹配單一字符,而或選項則用于從字符串集或正則表達式集中尋找匹配項。
? ? ? 以下便利用grep結合echo作為演示實例。首先,我們進行一個簡單的字符串匹配。
? ? ? shell> echo "AAA" | grep -i "aaa" ?=> 結果輸出AAA
? ? ? shell> echo "BBB" | grep -i "aaa" => ?沒有輸出結果
? ? ? 這是一個非常直白的例子,將echo的輸出結果送至grep進行匹配搜索。如果匹配成功,結果便輸出打印出來;如無匹配項,則無結果輸出。
? ? ? 現在添加或選項,它用元字符"|"表示。
? ? ? shell> echo "AAA" | grep -iE "aaa|bbb" ?=> 結果輸出AAA
? ? ? shell> echo "BBB" | grep -iE "aaa|bbb" ?=> 結果輸出BBB
? ? ? shell> echo "CCC" | grep -iE "aaa|bbb" ?=> 沒有輸出結果
? ? ? 這里出現了'AAA|BBB'正則表達式,此表達式的含義是“匹配字符串AAA或者匹配字符串BBB”。請注意,由于此處使用的是擴展特性,所以grep命令增加了-E選項(雖然可以使用egrep命令來代替),并且將正則表達式用引號引起來以防止shell將元字符“|”當作管道操作符來處理。另外,或選項并不局限于兩種選擇,還可以有更多的選擇項。例如:
? ? ? shell> echo "AAA" | grep -iE "aaa|bbb|ccc|ddd" ?=> 結果輸出為AAA
? ? ? 為了將或選項可與其他正則表達式符號結合使用,我們可以用“()”將或選項的所有元素與其他符號隔開。
? ? ? shell> grep -Eh '^(bz|gz|zip)' dirlist*.txt ? ? ?
? ? ? 以上表達式的含義是匹配文件名以bz、gz或是zip開頭的文件。如果不使用括號,該正則表達式的含義就完全不同,其匹配的便是文件名以bz開頭或者是包含gz和zip的文件。
?
九、限定符
? ? ? 擴展正則表達式(ERE)提供多種方法指定某元素匹配的次數。
? ? ? 該限定符實際上意味著“前面的元素可選”。比如,我們想檢查某電話號碼的有效性。所謂電話號碼有效,指的是電話號碼必須是下面兩種形式(nnn)nnn-nnnn和nnn nnn-nnnn中的一種,其中n是數值。于是,我們可以構造如下所示的正則表達式。
? ? ? ^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$
? ? ? 此表達式中,括號字符的后面增加了“?”符號以表示括號字符只能匹配一次或零次。同樣,由于括號字符在ERE中通常是元字符,所以其前面加上了反斜杠告訴shell此括號為文字字符。實例如下圖:
?
? ? ? 2. *——匹配某元素0次或多次
? ? ? 與“?”元字符類似,“*”用于表示一個可選擇的條目。然而,與“?”不同,該條目可以出現多次,而不僅僅是一次。例如,如果我們想知道一串字符是否是一句話,也就是說,這串字符是否以大寫字母開頭而以句話結束,并且中間內容是任意數目的大小寫字母和空格,那么要匹配這種非常粗糙的句子定義,可以用如下正則表達式:
? ? ? [[:upper:]][[:upper:][:lower:] ]*\.
? ? ? 該表達式包含了三個條目:包含[:upper:]字符類的中括號表達式、包含[:upper:]和[:lower:]兩個字符類以及一個空格的中括號表達式、用反斜杠轉義過的原點符號。第二個條目后面緊跟著“*”元字符,所以只要句子的第一個字母是大寫字母,后面不管會出現多少數目的大小寫字母都無關緊要。樣例如下圖:
? ? ? 該表達式匹配第一個測試語句,但是不匹配第二個。原因是它的首字母不是大寫。
?
? ? ? 3. +——匹配某元素一次或多次
? ? ? “+”元字符與“*”非常類似,只是“+”要求置于其前面的元素至少出現一次。示例如下:該正則表達式用于匹配由單個空格分隔的一個或者多個字母字符組成的行。
? ? ? ^([[:alpha:]]+ ?)+$
? ? ? 示例如下圖所示:
?
? ? ? 4. {}——以指定次數匹配某元素
? ? ? “{”和“}”元字符用于描述最小和最大次數的需求匹配??梢酝ㄟ^4種方法來指定。見下表所示:
| 指定項 | 含義 |
| {n} | 前面的元素恰好出現n次則匹配 |
| {n,m} | 前面的元素出現的次數在n~m次之間時則匹配 |
| {n,} | 前面的元素出現次數超過n次則匹配 |
| {,m} | 前面的元素出現的次數不超過m次則匹配 |
? ? ? 回到前面電話號碼的例子,我們可以運用此處講到的指定重復次數的辦法將原來的正則表達式"^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$"簡化為“^\(?[0-9]{3} [0-9]{3}-[0-9]{4}\)?$”。具體如下圖所示:
?
轉載于:https://www.cnblogs.com/jplatformx/p/4322464.html
總結
以上是生活随笔為你收集整理的(021) Linux之正则表达式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 早起习惯养成尝试2
- 下一篇: Linux基本命令(1)管理文件和目录的