Linux正規表示式詳解

joshliu發表於2023-12-11

背景

    正規表示式(Regular Expression)的概念和發展可以追溯到20世紀50年代和60年代的電腦科學和形式語言理論的研究。正規表示式的發展涉及到多位科學家和電腦科學家的貢獻。其中,美國電腦科學家和數學家Stephen Cole Kleene(斯蒂芬·科爾·克萊尼)被認為是正規表示式的先驅。他在20世紀50年代的研究中,提出了正則語言(Regular Language)的概念和形式化定義,為正規表示式的發展奠定了理論基礎。他的研究成果在形式語言理論和電腦科學中有重要的影響。

    在正規表示式的實際應用和發展中,美國電腦科學家和Unix作業系統的創始人之一Ken Thompson(肯·湯普森)也發揮了重要的作用。在20世紀70年代,他在貝爾實驗室開發的Unix作業系統中引入了正規表示式的概念和實現,將正規表示式作為一種強大的文字處理工具引入了電腦科學和軟體開發的領域。

    簡單來說,透過正規表示式,可以模糊匹配到滿足條件的項,繼而實現對該項的多維度操作。一般而言,Linux系統上經常使用到正規表示式的命令有vi、sed、grep、awk。


格式

    正規表示式在Linux實際使用時候,格式很簡單,一般表示如下:

    /pattern/(sed/awk)或者 'pattern'(grep)

    其中,pattern(模式)代表需要進行匹配的內容,在Linux中,pattern分為普通正則以及擴充套件正則兩種模式,在擴充套件正則下,新增/修改了部分pattern,常用的pattern請見下一章節。

     *注:如果需要使用擴充套件正則,在sed/grep中,請使用-E引數。但在awk中,預設就是擴充套件正則形式。


模式

    假如有一個需求,想要把a.txt中包括關鍵字test的行取出來,通常情況下有如下三種方式:

  1.     sed -n '/test/p' a.txt

  2.     grep 'test' a.txt

  3.     awk '/test/' a.txt

    這裡,完整的單詞test就屬於一種pattern(模式),表示匹配test這個單詞,當然,除了用完整的單詞作為pattern,我們還可以使用如下正規表示式來作為pattern進行匹配:


普通正則

     字元

  • 普通字元,包括“test”或者“hello”等

  • 字元類,比如[],所以[abc%8!]表示a或者b或者c或者%或者8或者!中任意一個

  • 範圍類,比如-,[a-d]表示一個小寫字母a/b/c/d。同理[0-9]表示任意一個數字;[a-z0-9A-Z]表示任意一個字母或數字

  • 反向字元類,用^表示的反向字元類匹配不在字元類中的任意字元。例如正規表示式[^0-9]匹配一個任意非數字字元。

  • 通用類,用.表示除\n\s之外的任意一個字元

    

     重複

  • *,表示前面的字元0次或者多次

  • \+,表示前面的字元1次或多次

    *注:如果在awk中使用該功能,請參考擴充套件正則

  • \?,表示前面的字元0次或者1次

    *注:如果在awk中使用該功能,請參考擴充套件正則

  • \{m\},表示前面的字元重複m次

    *注:如果在awk中使用該功能,請參考擴充套件正則,下同

  • \{m,\},表示前面的字元重複m以及m次以上

  • \{,n\},表示前面的字元重複m次以內(包括0次)

  • \{m,n\},表示前面的字元重複m到n次(閉區間,並且n>=m)


     特殊用法

  • ^,表示行開頭,這裡需要注意與[]中的^區別,比如^[^abc]表示以非a/b/c字元開頭

  • $,表示行結尾

  • \|,表示或者

    *注:如果在awk中使用該功能,請參考擴充套件正則

  • \b,表示邊界,比如說\bthis可以匹配thisabc或者%this或者this,但不能匹配tttthis

    *注:該方法不能在awk中使用

  • \<,表示左邊界

  • \>,表示右邊界

  • \,用於轉義特殊字元

  • \(\),表示整體(可以改變運算子的優先順序)或者將匹配項捕獲到\1\2中

    *注:如果在awk中使用該功能,請參考擴充套件正則。另,awk取出匹配內容不能使用\1或者\2,而是透過match捕獲

  • \1,表示使用\(...\)所取出來的第一個內容,比如說模式\([a-z]\)\1,表示連續的兩個相同小寫字母

    *注:該方法不能在awk中使用


擴充套件正則

     重複

  • {m},表示前面的字元重複m次

  • {m,},表示前面的字元重複m以及m次以上

  • {,n},表示前面的字元重複m次以內(包括0次)

  • {m,n},表示前面的字元重複m到n次(閉區間,並且n>=m)

  • ?,表示前面的字元重複0或者1次,等價於{,1}或者{0,1};同理,*等價於{0,}

  • +,表示前面的字元重複1次或者多次,等價於{1,}


     特殊用法

  • |,表示或者

  • (), 表示整體(可以改變運算子的優先順序)或者將匹配項捕獲到\1\2中

    可以發現,擴充套件正則是對普通正則的一種簡化,為了避免不必要的歧義以及麻煩,建議使用者在使用正則時候,直接透過-E引數來在sed/grep中使用擴充套件正則。


樣例

    我們假設有b.txt檔案如下:

    [root@n110 ~]# cat b.txt

    xxx1

    xx1

    x1

    x

    x+

    x*

    *.*

    .

    .+

    thisis

    thatis

    thisab

    thatab

    thab

    tatle

    ttle

    hab

    %hab+

    xyz!abc123def

    456xyzabc123def

  • 列印x出現連續3次的行

grep 'x\{3\}' b.txt #或者 grep -E 'x{3}' b.txt
sed -n '/x\{3\}/p' b.txt #或者 sed -n -E '/x{3}/p' b.txt
awk '/x{3}/ {print}' b.txt


  • 列印x+英文小寫字母+y的行

grep 'x[a-z]y' b.txt
sed -n '/x[a-z]y/p' b.txt
awk '/x[a-z]y' b.txt


  • 列印x+x(出現0次或者多次)+y的行

grep 'xx*y' b.txt
sed -n '/xx*y/p' b.txt
awk '/xx*y/' b.txt


  • 列印x+(x或者y)+x的行

grep 'x[xy]x' b.txt
sed -n '/x[xy]x/p' b.txt
awk '/x[xy]x/' b.txt


  • 列印以xx開頭的行

grep '^xx' b.txt
sed -n '/^xx/p' b.txt
awk '/^xx/' b.txt


  • 列印不是以小寫字母x或者t開頭的行

grep '^[^xt]' b.txt
sed -n '/^[^xt]/p' b.txt
awk '/^[^xt]/' b.txt


  • 列印包括tha或者tat的行

grep 'tha\|tat' b.txt  #或者 grep -E 'tha|tat' b.txt
sed -n '/tha\|tat/p' b.txt #或者 sed -n -E '/tha|tat/p' b.txt
awk '/tha|tat/' b.txt


  • 列印t+ha或者a+t的行

grep -E 't(ha|a)t' b.txt
sed -n -E '/t(ha|a)t/p' b.txt
awk '/t(ha|a)t/' b.txt

     *注:此處是()的運用之一,將所括部分當成整體來匹配,當然,括號還可以用以結合\1以及\2,匹配更多的內容


  • 列印連續兩個相同小寫字母的行

grep -E '([a-z])\1' b.txt
sed -n -E '/([a-z])\1/p' b.txt

     *注:括號部分將一個小寫字母選中,後續的\1代表該選中內容,如果能夠連續匹配,則表示兩個相同小寫字母

    下面我們來看幾個更加複雜的例子。


  • 如果某行匹配連續小寫字母+連續數字,那麼列印匹配的字母部分

grep -o -E '[a-z]+[0-9]+' b.txt | grep -o -E '[a-z]+' #這裡-o參數列示僅輸出匹配部分
sed -n -E 's/(.*[^a-z]|^)([a-z]+)[0-9]+.*/\2/p' b.txt #這裡將整行資料全部替換成取到的\2,因為匹配[a-z]+[0-9]+的前一個字元要麼是開頭,要麼是.*加上一個非[a-z]的字元
awk 'match($0,/([a-z]+)[0-9]+/,arr) {print arr[1]}' b.txt #這裡使用awk中的match語法,將匹配的內容存放在陣列arr中,注意arr[0]代表正則中所能匹配的所有內容,arr[1]代表第一個括號所匹配的內容,arr[2]代表第二個括號匹配內容。

     *注:如果單行中出現多個[a-z]+[0-9]+,比如說xyz! abc123def456這種

  • 上述grep命令會將兩次 匹配的[a-z]+[0-9]+中[a-z]+內容列印出來,即abc與def都會列印在不同行。如果需要列印第一個匹配的內容中[a-z]+部分,請使用-m引數控制輸出行

  • 上述sed命令會列印第二次匹配的[a-z]+[0-9]+中[a-z]+內容,也就是字串def(.*的貪婪匹配)

  • 上述awk命令仍然只會列印第一次匹配到的內容


  • 列印is是右邊界的行

grep 'is\b' b.txt 或者 grep 'is\>' b.txt
sed -n 's/is\b/p' b.txt 或者 sed -n 's/is\>/p' b.txt
awk '/is\>' b.txt


  • 列印僅匹配單詞hab的行(注:連續字母thab不能被列印)

grep '\bhab\b' b.txt
grep '\<hab\>' b.txt
sed -n '/\bhab\b/p' b.txt
awk '/\<hab\>/' b.txt
grep -w 'hab' b.txt # 直接使用-w引數(word)


  • 列印x+a或者b出現0次或多次+y

grep -E 'x[ab]{0,}y' b.txt
grep 'x[ab]*y' b.txt
sed -n '/x[ab]*y/p' b.txt
sed -n -E  '/x[ab]{0,}y/p' b.txt


  • 將第一次出現的連續字母+連續數字的位置對調,並列印匹配行中對調後內容

sed -n -E 's/([a-z]+)([0-9]+)/\2\1/p' b.txt
awk 'match($0,/([a-z]+)([0-9]+)/,arr) {print arr[2] arr[1]}' b.txt

    以上內容基本涵蓋了常用的正則匹配,但在實際使用當中,還是需要將正則內容組合起來,實現更復雜內容的匹配與選取。我們再看下面幾個例子:

    [root@n110 ~]# cat d.txt

    this is cat!

    this is THE cat!

    this is THE THE cat!

    THE THE THE cat!

    this is THE THE THE cat!

    this is THE THAT THE cat!

    this is THE THE THE THE cat!

    this is THE THE THE THE THEcat!

    this isTHE THE THE THE THEcat!

    this is A A A cat!

    123

    thisis123


  • 去掉d.txt中重複的獨立單詞THE

    第一反應通常是將連續的THE替換成第一個,我們嘗試命令如下:

sed -E 's/(THE )+/\1/g' d.txt

    上述紅框內容出現了異常,按理說原有該行內容為“ this isTHE THE THE THE THEcat!”,去除完獨立THE之後,應該剩餘“ this isTHE THE THEcat!”,出現紅框中原因是isTHE也被當成一個獨立的THE,所以為了避免該錯誤,我們改進命令如下:

sed -E 's/\b(THE )+/\1/g' d.txt

    此時顯示正確!


    另外,該命令還可以使用如下命令代替:

sed -E 's/\b(THE )\1+/\1/g' d.txt #先出現一個獨立的THE,再用\1代表該內容,使用+表示該內容出現1次或者多次,最後替換成\1,也就是不管幾次匹配,最後都替換成一次匹配


  • 去掉d.txt中所有重複的獨立單詞(依據d.txt內容來看,不僅需要去除連續單詞THE,還要去除連續單詞A)

    第一個想到的方法,就是將連續的單詞替換成\1:

sed -E 's/\<([a-zA-Z]+ )+/\1/g' d.txt

    不過我們發現結果完全不對,原因在於([a-zA-Z]+ )上,因為該部分內容匹配任何的英文字母,而不是任何重複的英文字母,比如說“this is character A”,上述正則既匹配“this ”,也匹配“is ”,同時也匹配“character ”,但不匹配最後一個單詞“A”。而且,如果([a-zA-Z]+ )匹配多處內容,那\1會以最後一處內容為準:

     *注:關於sed 's///1'中的1表示一行中第一個匹配的內容。

    回到最初的問題: 去掉d.txt中所有重複的獨立單詞,此時我們就需要改進命令如下:

sed -E 's/\<([a-zA-Z]+ )\1+/\1/g' d.txt #先取出連續的字母,再使用\1+表示同樣的內容重複多次,最後替換成該內容


總結

    Linux的正則是用來處理檔案最強大的手段之一,可以用在grep/sed/awk以及vi中,結合這些命令本身來進行查詢、列印以及替換等功能。有了正則,我們可以用來匹配任何文字內容,包括字母、數字、開頭/結尾、任意指定的字元、邊界以及重複的次數等等。一個典型的正規表示式通常由 /開頭(字元A|字元B)重複次數+特殊用法/組成,在Linux的正則中,有多種方式來表示“字元”,同樣也有多種方式來表示“重複次數”。另外,需要注意的是普通正則與擴充套件正則的異同,為了簡便起見,我們建議直接使用擴充套件正則來代替普通正則以達到易於書寫並且方便閱讀的目的。

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/69985104/viewspace-2999485/,如需轉載,請註明出處,否則將追究法律責任。

相關文章