正則表達式簡明教程 - grep Vim的查找與替換實例

jopen 11年前發布 | 31K 次閱讀 Vim

概論:

      在各種有關文本處理的程序中,往往要用到正則表達式。熟練掌握正則表達式,是一項基本技能。本文,主要說明正則表達式的原理與應用,并給出了詳細例子,用于情景學習,無論是使用VIM ,sed,awk,grep等程序,都能在本文著那個找到幫助。

1.基礎知識

      什么是字符?

      字符,符號簡單的說就是抽象,一定的符號表示一定的意義。一般來說,字符含義有三種“一般字符、特殊字符、轉義字符”。例如在ASCII中,a就表示a(一般字符);/表示轉意后面一個字符,%表示格式化輸出(特殊字符);\n表示回車(轉義字符),就是讓n不代表它本身的意義(一般意義或者特殊意義).同時,轉義字符可以讓一個一般字符也表達特殊意義如\a,也可以讓一個特殊字符表達一般意義/%。 不同語言體系的特殊字符不同,轉意字符也不同。例如在漢語中,“。”表示句子的終結,但是放在轉義字符“”之中就僅僅表示“。”。(所以,從理論上說,只 要你喜歡,你完全可以把所有的轉義字符和格式化特殊字符都用普通字符來表示,不過此時,你的符號系統需要更多的字符來區分)

2.正則表達式

      如你所知,我們通常意義上說的正則表達式其實包含兩部分:正則表達式的處理引擎(grepC#等程序中都包含)和正則表達式語法,一定的語法要輸入在引擎中才能起作用,理解這一點很重要。而本文主要為你講述正則表達式的語法。

     下面,談一談正則表達式中的轉義字符(注意,不同的語系中特殊字符的含義不同,例如C語言中$是一般字符,但在正則表達式中卻不是):

     2.1特殊字符:

     /  [  ]   ^   %   $   *   +  .   |   (  )   –

特別字符

</td>

說明

</td> </tr>

$

</td>

匹配輸入字符串的結尾位置。如果設置了 RegExp 對象的 Multiline 屬性,則 也匹配 ‘\n' 或‘\r'。要匹配 字符本身,請使用 \$

</td> </tr>

()

</td>

標記一個子表達式的開始和結束位置。子表達式可以獲取供以后使用。要匹配這些字符,請使用 \( 和 \)

</td> </tr>

*

</td>

匹配前面的子表達式零次或多次。要匹配 字符,請使用 \*

</td> </tr>

+

</td>

匹配前面的子表達式一次或多次。要匹配 字符,請使用 \+

</td> </tr>

.

</td>

匹配除換行符 \n之外的任何單字符。要匹配 .,請使用 \

</td> </tr>

[]

</td>

標記一個中括號表達式的開始。要匹配 [,請使用 \[

</td> </tr>

?

</td>

匹配前面的子表達式零次或一次,或指明一個非貪婪限定符。要匹配 字符,請使用 \?

</td> </tr>

\

</td>

將下一個字符標記為或特殊字符、或原義字符、或向后引用、或八進制轉義符。例如, ‘n' 匹配字符 ‘n''\n' 匹配換行符。序列 ‘\\' 匹配 “\”,而 ‘\(' 則匹配 “(”

</td> </tr>

^

</td>

匹配輸入字符串的開始位置,除非在方括號表達式中使用,此時它表示不接受該字符集合。要匹配 字符本身,請使用 \^

</td> </tr>

{}

</td>

標記限定符表達式的開始。要匹配 {,請使用 \{

</td> </tr>

|

</td>

指明兩項之間的一個選擇。要匹配 |,請使用 \|

</td> </tr> </tbody> </table>


       2.2轉義

\d 
表示一個數位字符,等價與[0-9]. 
\D 
表示一個非數位字符,等價與[^0-9]. 
\f 
表示一個form-feed字符(unix). 
\n 
表示一個linefeed字符(新行符). 
\r 
表示一個carriagereturn字符(復位符). 
\s 
表示任意一個非換行符的whitespace(空白字符),包括空格,tab,form-feed. 
\S 
表示一個非換行符的non-whitespace(非空白字符). 
\t 
表示一個tab. 
\v 
表示一個縱向tab(unix). 
\w 
表示任意一個顯示字符,包括下劃線(數字,字母,特殊符號等,但不包括whitespace). 
\W 
表示任意一個非顯示字符(non-wordcharacter).


2.3元字符

  • 元字符

    </td>

    描述

    </td> </tr>

    \

    </td>

    將下一個字符標記為一個特殊字符、或一個原義字符、或一個向后引用、或一個八進制轉義符。例如,“\n”匹配字符“n”。“\\n”匹配一個換行符。序列“\\”匹配“\”而“\(”則匹配“(”

    </td> </tr>

    ^

    </td>

    匹配輸入字符串的開始位置。如果設置了RegExp對象的Multiline屬性,^也匹配“\n”或“\r”之后的位置。

    </td> </tr>

    $

    </td>

    匹配輸入字符串的結束位置。如果設置了RegExp對象的Multiline屬性,$也匹配“\n”或“\r”之前的位置。

    </td> </tr>

    *

    </td>

    匹配前面的子表達式零次或多次。例如,zo*能匹配“z”以及“zoo”*等價于{0,}

    </td> </tr>

    +

    </td>

    匹配前面的子表達式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”+等價于{1,}

    </td> </tr>

    ?

    </td>

    匹配前面的子表達式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”?等價于{0,1}

    </td> </tr>

    {n}

    </td>

    n是一個非負整數。匹配確定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的兩個o

    </td> </tr>

    {n,}

    </td>

    n是一個非負整數。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等價于“o+”。“o{0,}”則等價于“o*”

    </td> </tr>

    {n,m}

    </td>

    mn均為非負整數,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”將匹配“fooooood”中的前三個o。“o{0,1}”等價于“o?”。請注意在逗號和兩個數之間不能有空格。

    </td> </tr>

    ?

    </td>

    當該字符緊跟在任何一個其他限制符(*,+,?{n}{n,}{n,m})后面時,匹配模式是非貪婪的。非貪婪模式盡可能少的匹配所搜索的字符串,而默認的貪婪模式則盡可能多的匹配所搜索的字符串。例如,對于字符串“oooo”,“o+?”將匹配單個“o”,而“o+”將匹配所有“o”

    </td> </tr>

    .

    </td>

    匹配除“\n”之外的任何單個字符。要匹配包括“\n”在內的任何字符,請使用像“[\s\S]”的模式。

    </td> </tr>

    (pattern)

    </td>

    匹配pattern并獲取這一匹配。所獲取的匹配可以從產生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中則使用$0…$9屬性。要匹配圓括號字符,請使用“\(”或“\)”

    </td> </tr>

    (?:pattern)

    </td>

    匹配pattern但不獲取匹配結果,也就是說這是一個非獲取匹配,不進行存儲供以后使用。這在使用或字符“(|)”來組合一個模式的各個部分是很有用。例如“industr(?:y|ies)”就是一個比“industry|industries”更簡略的表達式。

    </td> </tr>

    (?=pattern)

    </td>

    正向肯定預查,在任何匹配pattern的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始。

    </td> </tr>

    (?!pattern)

    </td>

    正向否定預查,在任何不匹配pattern的字符串開始處匹配查找字符串。這是一個非獲取匹配,也就是說,該匹配不需要獲取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。預查不消耗字符,也就是說,在一個匹配發生后,在最后一次匹配之后立即開始下一次匹配的搜索,而不是從包含預查的字符之后開始。

    </td> </tr>

    (?<=pattern)

    </td>

    反向肯定預查,與正向肯定預查類似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”

    </td> </tr>

    (?

    </td>

    反向否定預查,與正向否定預查類似,只是方向相反。例如“(?能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”

    </td> </tr>

    x|y

    </td>

    匹配xy。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”則匹配“zood”或“food”

    </td> </tr>

    [xyz]

    </td>

    字符集合。匹配所包含的任意一個字符。例如,“[abc]”可以匹配“plain”中的“a”

    </td> </tr>

    [^xyz]

    </td>

    負值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“plin”

    </td> </tr>

    [a-z]

    </td>

    字符范圍。匹配指定范圍內的任意字符。例如,“[a-z]”可以匹配“a”到“z”范圍內的任意小寫字母字符。

    </td> </tr>

    [^a-z]

    </td>

    負值字符范圍。匹配任何不在指定范圍內的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范圍內的任意字符。

    </td> </tr>

    \b

    </td>

    匹配一個單詞邊界,也就是指單詞和空格間的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”

    </td> </tr>

    \B

    </td>

    匹配非單詞邊界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”

    </td> </tr>

    \cx

    </td>

    匹配由x指明的控制字符。例如,\cM匹配一個Control-M或回車符。x的值必須為A-Za-z之一。否則,將c視為一個原義的“c”字符。

    </td> </tr>

    \d

    </td>

    匹配一個數字字符。等價于[0-9]

    </td> </tr>

    \D

    </td>

    匹配一個非數字字符。等價于[^0-9]

    </td> </tr>

    \f

    </td>

    匹配一個換頁符。等價于\x0c\cL

    </td> </tr>

    \n

    </td>

    匹配一個換行符。等價于\x0a\cJ

    </td> </tr>

    \r

    </td>

    匹配一個回車符。等價于\x0d\cM

    </td> </tr>

    \s

    </td>

    匹配任何空白字符,包括空格、制表符、換頁符等等。等價于[\f\n\r\t\v]

    </td> </tr>

    \S

    </td>

    匹配任何非空白字符。等價于[^\f\n\r\t\v]

    </td> </tr>

    \t

    </td>

    匹配一個制表符。等價于\x09\cI

    </td> </tr>

    \v

    </td>

    匹配一個垂直制表符。等價于\x0b\cK

    </td> </tr>

    \w

    </td>

    匹配包括下劃線的任何單詞字符。等價于“[A-Za-z0-9_]”

    </td> </tr>

    \W

    </td>

    匹配任何非單詞字符。等價于“[^A-Za-z0-9_]”

    </td> </tr>

    \xn

    </td>

    匹配n,其中n為十六進制轉義值。十六進制轉義值必須為確定的兩個數字長。例如,“\x41”匹配“A”。“\x041”則等價于“\x04&1”。正則表達式中可以使用ASCII編碼。

    </td> </tr>

    \num

    </td>

    匹配num,其中num是一個正整數。對所獲取的匹配的引用。例如,“(.)\1”匹配兩個連續的相同字符。

    </td> </tr>

    \n

    </td>

    標識一個八進制轉義值或一個向后引用。如果\n之前至少n個獲取的子表達式,則n為向后引用。否則,如果n為八進制數字(0-7),則n為一個八進制轉義值。

    </td> </tr>

    \nm

    </td>

    標識一個八進制轉義值或一個向后引用。如果\nm之前至少有nm個獲得子表達式,則nm為向后引用。如果\nm之前至少有n個獲取,則n為一個后跟文字m的向后引用。如果前面的條件都不滿足,若nm均為八進制數字(0-7),則\nm將匹配八進制轉義值nm

    </td> </tr>

    \nml

    </td>

    如果n為八進制數字(0-3),且ml均為八進制數字(0-7),則匹配八進制轉義值nml

    </td> </tr>

    \un

    </td>

    匹配n,其中n是一個用四個十六進制數字表示的Unicode字符。例如,\u00A9匹配版權符號(?)。

    </td> </tr> </tbody> </table>


    2.4正則表達式語法支持情況

    命令或環境

    </td>

    .

    </td>

    []

    </td>

    ^

    </td>

    $

    </td>

    \(\)

    </td>

    \{\}

    </td>

    ?

    </td>

    +

    </td>

    |

    </td>

    ()

    </td> </tr>

    vi

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>


    </td>


    </td>


    </td>


    </td> </tr>

    VisualC++

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>


    </td>


    </td>


    </td>


    </td> </tr>

    awk

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>


    </td>

    </td>

    </td>

    </td>

    </td> </tr>

    sed

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>


    </td>


    </td>


    </td> </tr>

    Tcl

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>

    </td>

    </td>

    </td>

    </td> </tr>

    ex

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>


    </td>


    </td>


    </td> </tr>

    grep

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>


    </td>


    </td>


    </td> </tr>

    egrep

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>

    </td>

    </td>

    </td>

    </td> </tr>

    fgrep

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>


    </td>


    </td>


    </td>


    </td> </tr>

    perl

    </td>

    </td>

    </td>

    </td>

    </td>

    </td>


    </td>

    </td>

    </td>

    </td>

    </td> </tr>

    C#

    </td>


    </td>


    </td>


    </td>


    </td>

    </td>

    </td>


    </td>


    </td>


    </td>


    </td> </tr> </tbody> </table>


    1. </ol>

      2.5.實例分析:

      在接下來的實例分析中,我們主要使用grepsed作為測試工具,測試為regular_expression.txt處理以后的文件,可以在我的資源里下載,然后更重命名a.txt,進行測試。

      1)實例一:

        查找所有長度為四的單詞:\b[a-p]\{4\}\b

        注意其中{}之前的\

      解釋:\b(單詞邊界)[a-p](字母ap){4}(出現4次,注意要加轉義符號)

      命令效果圖:

      正則表達式簡明教程 - grep Vim的查找與替換實例

      2)實例2(注意:sed并沒有改變原來文本)

      將所有方法foo(a,b,c)的實例改為foo(b,a,c)。這里abc可以是任何提供給方法foo()的參數。也就是說我們要實現這樣的轉換:

      之前之后

      foo(10,7,2)——foo(7,10,2)

      foo(x+13,y-2,10)——foo(y-2,x+13,10)

      foo(bar(8),x+y+z, 5) ——foo( x+y+z, bar(8), 5)

      下面這條替換命令能夠實現這一魔法:

      :%s/foo(\([^,]*\),\([^,]*\),\([^)]*\))/foo(\2,\1,\3)/g

      這條命令在Vim中使用,效果圖:

      正則表達式簡明教程 - grep Vim的查找與替換實例

        sed命令使用效果圖(注意:不能少“”也不能多-e選項,要嚴格區分正則匹配和字符串匹配):

      hyk@hyk-linux:~$sed "s/foo(\([^,]*\),\([^,]*\),\([^)]*\))/foo(\2,\1,\3)/"a.test

      foo(7,10,2)

      foo(y-2,x+13,10)

      foo(x+y+z,bar(8), 5)

      說明:只選取了相關部分

              現在讓我們把它打散來加以分析。寫出這個表達式的基本思路是找出foo()和它的括號中的三個參數的位置。第一個參數是用這個表達式來識別的::\([^,]*\),我們可以從里向外來分析它:

      [^,]除了逗號之外的任何字符

      [^,]* 0或者多個非逗號字符

      \([^,]*\)將這些非逗號字符標記為\1,這樣可以在之后的替換模式表達式中引用它

      \([^,]*\),我們必須找到0或者多個非逗號字符后面跟著一個逗號,并且非逗號字符那部分要標記出來以備后用。注意()的\

             現在正是指出一個使用正則表達式常見錯誤的最佳時機。為什么我們要使用[^,]*這樣的一個表達式,而不是更加簡單直接的寫法,例如:.*,來匹配第一個參數呢?設想我們使用模式.*來匹配字符串"10,7,2",它應該匹配"10,"還是"10,7,"?為了解決這個兩義性(ambiguity),正則表達式規定一律按照最長的串來,在上面的例子中就是"10,7,",顯然這樣就找出了兩個參數而不是我們期望的一個。所以,我們要使用[^,]*來強制取出第一個逗號之前的部分。

              這個表達式我們已經分析到了:foo(\([^,]*\),這一段可以簡單的翻譯為“當你找到foo(就把其后直到第一個逗號之前的部分標記為\1”。然后我們使用同樣的辦法標記第二個參數為\2。對第三個參數的標記方法也是一樣,只是我們要搜索所有的字符直到右括號。我們并沒有必要去搜索第三個參數,因為我們不需要調整它的位置,但是這樣的模式能夠保證我們只去替換那些有三個參數的foo()方法調用,在foo()是一個重載overloading)方法時這種明確的模式往往是比較保險的。然后,在替換部分,我們找到foo()的對應實例,然后利用標記好的部分進行替換,是把第一和第二個參數交換位置。

      3)實例3

              假設有一個CSVcommaseparatedvalue)文件,里面有一些我們需要的信息,但是格式卻有問題,目前數據的列順序是:姓名,公司名,州名縮寫,郵政編碼,現在我們希望將這些數據重新組織,以便在我們的某個軟件中使用,需要的格式為:姓名,州名縮寫-郵政編碼,公司名。也就是說,我們要調整列順序,還要合并兩個列來構成一個新列。另外,我們的軟件不能接受逗號前后有任何空格(包括空格和制表符)所以我們還必須要去掉逗號前后的所有空格。

      這里有幾行我們現在的數據:

      BillJones,HI-TEK Corporation , CA, 95011

      SharonLeeSmith, Design Works Incorporated, CA, 95012

      B.Amos, Hill Street Cafe, CA, 95013

      AlexanderWeatherworth,The Crafts Store, CA, 95014

      ...

      我們希望把它變成這個樣子:

      BillJones,CA95011,HI-TEK Corporation

      SharonLeeSmith,CA 95012,Design Works Incorporated

      B.Amos,CA95013,Hill Street Cafe

      AlexanderWeatherworth,CA95014,The Crafts Store

      ...

      我們將用兩個正則表達式來解決這個問題。第一個移動列和合并列,第二個用來去掉空格。

      下面就是第一個替換命令:

      :%s/\([^,]*\),\([^,]*\),\([^,]*\),\(.*\)/\1,\3\4,\2/

      結果如下

      35BillJones, CA 95011, HI-TEK Corporation

      36SharonLee Smith, CA 95012, Design Works Incorporated

      37B.Amos , CA 95013, Hill Street Cafe

      38AlexanderWeatherworth, CA 95014, The Crafts Store

      這里的方法跟例1基本一樣,第一個列(姓名)用這個表達式來匹配:\([^,]*\),即第一個逗號之前的所有字符,而姓名內容被用\1標記下來。公司名和州名縮寫字段用同樣的方法標記為\2\3,而最后一個字段用\(.*\)來匹配("匹配所有字符直到行末")。替換部分則引用上面標記的那些內容來進行構造。

      下面這個替換命令則用來去除空格:

      :%s/[\t]*,[\t]*/,/g

      結果如下:

      35BillJones,CA 95011,HI-TEK Corporation

      36SharonLee Smith,CA 95012,Design Works Incorporated

      37B.Amos ,CA 95013,Hill Street Cafe

      38AlexanderWeatherworth,CA 95014,The Crafts Store

      我們還是分解來看:[\t]匹配空格/制表符[\t]* 匹配0或多個空格/制表符,[\t]*,匹配0或多個空格/制表符后面再加一個逗號,最后,[\t]*,[\t]*匹配0或多個空格/制表符接著一個逗號再接著0或多個空格/制表符。在替換部分,我們簡單的我們找到的所有東西替換成一個逗號。這里我們使用了結尾的可選的g參數,這表示在每行中對所有匹配的串執行替換(而不是缺省的只替換第一個匹配串)。

      3)實例4

      假設有一個多字符的片斷重復出現,例如:

      Billytriedreally hard

      Sallytriedreally really hard

      Timmytriedreally really really hard

      Johnnytriedreally really really really hard

      而你想把"really""reallyreally",以及任意數量連續出現的"really"字符串換成一個簡單的"very"simpleisgood!),那么以下命令:(注意空格)

      :%s/\(really\)\(really \)*/very /

      就會把上述的文本變成:

      Billytriedvery hard

      Sallytriedvery hard

      Timmytriedvery hard

      Johnnytriedvery hard

      表達式\(really\)*匹配0或多個連續的"really"(注意結尾有個空格),而\(really\)\(really\)* 匹配1個或多個連續的"really"實例。

      結果如下

      40Billytried very hard

      41Sallytried very hard

      42Timmytried very hard

      43Johnnytried very hard


      外部參考:

      [1]Unix之父將此符號系統引入編輯器QED,然后是Unix上的編輯器ed,并最終引入grepJeffreyFriedl在其著作“MasteringRegular Expressions (2ndedition)/中文版譯作:精通正則表達式,目前已出到第三版”中對此作了進一步闡述講解,如果你希望更多了解正則表達式理論和歷史,推薦你看看這本書。

            [2]百度百科:http://baike.baidu.com/view/94238.htm

      來自:http://blog.csdn.net/trochiluses/article/details/8827932

       本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
       轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
       本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!
  • sesese色