JavaRush /Java Blog /Random-TW /RegEx:掌握正規表示式的 20 個簡短步驟。第 4 部分
Artur
等級 40
Tallinn

RegEx:掌握正規表示式的 20 個簡短步驟。第 4 部分

在 Random-TW 群組發布
RegEx:掌握正規表示式的 20 個簡短步驟。第 1 部分 RegEx:掌握正規表示式的 20 個簡短步驟。第 2 部分 掌握正規表示式的 20 個簡短步驟。第三部分 中間的最後一部分將涉及正規表示式大師主要使用的東西。但前面部分的材料對你來說很容易,對吧?這意味著您可以同樣輕鬆地處理這種材料!原文 RegEx:掌握正規表示式的 20 個簡短步驟。 第 4 - 1 部分 <h2>第 16 步:不捕獲的分組(?:)</h2> RegEx:掌握正規表示式的 20 個簡短步驟。 第 4 - 2 部分在上一步的兩個例子中,我們捕獲了並不真正需要的文本。在檔案大小任務中,我們捕獲了檔案大小第一個數字之前的空格,在 CSV 任務中,我們捕獲了每個標記之間的逗號。我們不需要捕獲這些字符,但我們確實需要使用它們來構造我們的正則表達式。這些是使用群組而不捕獲的理想選擇(?:)。非捕獲組的作用正如它聽起來的那樣 - 它允許對字元進行分組並在正則表達式中使用,但不會將它們捕獲在編號組中:
模式:(?:")([^"]+)(?:")
字串:我只想要"這些引號內的文字"。
配對:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
組:                 11111111111111111111111111111    
範例)正規表示式現在符合引用的文字以及引用字元本身,但捕獲組僅捕獲引用的文字。我們為什麼要這樣做?關鍵是大多數正規表示式引擎允許您從正規表示式中定義的捕獲組恢復文字。如果我們可以修剪不需要的額外字元而不將它們包含在我們的捕獲組中,那麼稍後解析和操作文字將變得更加容易。以下是清理上一個步驟中的 CSV 解析器的方法:
模式:(?:^|,)\s*(?:\"([^",]*)\"|([^", ]*))
字串:   a , " b ", " cd ", e , f , " gh ", dfgi ,, k , "", l
匹配:^ ^ ^^^ ^ ^ ^^^ ^^^^ ^ ^
組:    2 1 111 2 2 111 2222 2 2    
範例)這裡有一些事情需要<mark>注意:</mark>首先,自從我們將捕獲組更改(^|,)為非捕獲組以來,我們不再捕獲逗號(?:^|,)。其次,我們將捕獲組嵌套在非捕獲組中。例如,當您需要一組字元以特定順序出現,但您只關心這些字元的子集時,這非常有用。在我們的例子中,我們需要引號字元和逗號[^",]*出現在引號中,但實際上我們並不需要引號字元本身,因此不需要捕獲它們。k最後,<mark>注意</mark>在上面的範例中,字元和之間也存在零長度匹配l。引號""是搜尋到的子字串,但引號之間沒有字符,因此匹配的子字串不包含任何字符(長度為零)。<h3>我們要鞏固我們的知識嗎?以下兩個半任務將幫助我們完成此任務:</h3> 使用非捕獲組(以及捕獲組和字符類等),編寫一個僅捕獲格式正確的文件大小的正則表達式以下 :
圖案:
字串:   6.6KB 1..3KB 12KB 5G 3.3MB KB .6.2TB 9MB。
配對: ^^^^^ ^^^^^ ^^^^^^ ^^^^
群組:    11111 1111 11111 111    
解決方案) HTML 開始標記以 開始<,以 結束>。HTML 結束標記以字元序列開始</並以字元結束>。標籤名稱包含在這些字元之間。您可以編寫一個正規表示式來僅捕獲以下標記中的名稱嗎?(您也許可以在不使用非捕獲組的情況下解決此問題。嘗試透過兩種方式解決此問題!一次使用組,一次不使用組。)
圖案:
字串:   <p> </span> <div> </kbd> <link>
符合:^^^ ^^^^^^ ^^^^^ ^^^^^^ ^^^^^^
組:    1 1111 111 111 1111    
(使用非捕獲組的解決方案) (不使用非捕獲組的解決方案) <h2>第 17 步:反向連結\N和命名捕獲組</h2> RegEx:掌握正規表示式的 20 個簡短步驟。 第 4 - 3 部分儘管我在簡介中警告您,嘗試使用正則表達式建立HTML 解析器通常導致心痛,最後一個範例很好地介紹了大多數正規表示式的另一個(有時)有用的功能:反向引用。反向連結就像重複組,您可以嘗試兩次捕獲相同的文字。但它們在一個重要方面有所不同——它們只會逐個字符地捕獲相同的文本。雖然重複組可以讓我們捕捉如下內容:
模式:(he(?:[az])+)
字串:   heyabcdefg hey heyo heyellow heyyyyyyyyy
符合:^^^^^^^^^^^ ^^^ ^^^^ ^^^^^^^^ ^ ^^ ^^^^^^^^^
群:    1111111111 111 1111 11111111 11111111111    
範例)...那麼反向連結將僅符合以下內容:
模式:(he([az])(\2+))
字串:heyabcdefg hey heyo heyellow heyyyyyyyyy
符合:                             ^^^^^^^^^^^
群組:                                 11233333333    
範例)當您想要重複匹配相同的模式時,重複捕獲組非常有用,而當您想要匹配相同的文字時,反向連結很有用。例如,我們可以使用反向連結來嘗試尋找匹配的開始和結束 HTML 標記:
模式:<(\w+)[^>]*>[^<]+<\/\1>
字串:   <span style="color: red">hey</span>
符合:^^^^^^ ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
群組:    1111    
範例)<mark>請注意</mark>這是一個極其簡化的範例,我強烈建議您不要嘗試編寫基於正規表示式的 HTML 解析器。這是非常複雜的語法,很可能會讓您感到噁心。 命名捕獲組與反向連結非常相似,因此我將在這裡簡要介紹它們。反向引用和命名捕獲組之間的唯一區別是...命名捕獲組有一個名稱:
模式:<(?<tag>\w+)[^>]*>[^<]+<\/(?P=tag)></tag>
字串:   <span style="color: red">嘿< /span>
配對:^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
群組:    1111    
範例)您可以使用 (?<name>...) 或 (?'name'...) 語法(.NET 相容正規表示式)或使用此語法 (?P<name>. ..) 或( ?P'name'...) (Python 相容的正規表示式)。由於我們使用的是支援這兩個版本的 PCRE(Perl 相容正規表示式),因此我們可以在這裡使用其中一個。(Java 7 複製了 .NET 語法,但僅複製了尖括號版本。譯者註) 為了稍後在正規表示式中重複命名捕獲組,我們使用\<kname> 或\k'name' (.NET)或(? P= 名稱)(Python)。同樣,PCRE 支援所有這些不同的選項。您可以在此處閱讀有關命名捕獲組的更多信息,但這就是您真正需要了解的有關它們的大部分內容。<h3>幫助我們的任務:</h3>使用反向連結幫助我記住...嗯...這個人的名字。
圖案:
字串:“嗨,我叫喬。” [後來]“那個人叫什麼名字?喬?”
符合:       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^
組:                  111    
解決方案) <h2>第 18 步:前瞻和後瞻</h2> RegEx:掌握正規表示式的 20 個簡短步驟。 第 4 - 4 部分現在我們將深入探討正規表示式的一些進階功能。我經常使用步驟 16 之前的所有內容。但最後幾個步驟僅適用於非常認真地使用正規表示式來匹配非常複雜的表達式的人。換句話說,正規表示式大師。「展望未來」和「回顧過去」可能看起來很複雜,但實際上並不太複雜。它們允許您執行類似於我們之前對非捕獲組所做的操作 - 檢查我們要匹配的實際文字之前或之後是否有任何文字。例如,假設我們只想匹配人們喜歡的事物的名稱,但前提是他們對此充滿熱情(前提是他們以感嘆號結束句子)。我們可以做這樣的事情:
模式:(\w+)(?=!)
字串:我喜歡桌子。我很欣賞訂書機。我愛!
配對:                                          ^^^^
組:                                              1111    
範例)您可以看到上面的捕獲組(\w+)通常與段落中的任何單字匹配,但僅與單字 lamp 匹配。正向前瞻(?=!)意味著我們只能匹配以 結尾的序列!,但實際上並不匹配感嘆號字元本身。這是一個重要的區別,因為對於非捕獲組,我們匹配字元但不捕獲它。透過lookaheads和lookbehinds,我們使用一個字元來建立我們的正規表示式,但我們甚至不將它與自身進行匹配。我們可以稍後在正規表示式中匹配它。前瞻和後瞻有四種:正前瞻 (?=...)、負前瞻 (?!...)、正前瞻 (?<=...) 和負前瞻 (?<!. ..) 。它們的作用就像聽起來一樣 - 正向先行和後向允許正則表達式引擎僅在前向/後向中包含的文本實際匹配時繼續匹配。負向先行和後向執行相反的操作 - 僅當先行/後行中包含的文本不匹配時,它們才允許正則表達式匹配。例如,我們只想匹配方法序列鏈中的方法名稱,而不是它們操作的物件。在這種情況下,每個方法名稱前面必須帶有.. 使用簡單回顧的正規表示式可以在這裡提供幫助:
模式:(?<=\.)(\w+)
字串:myArray. flatMap.aggregate.summarise.print!
配對:        ^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^
群組:            1111111 111111111 111111111 11111    
範例)在上面的文字中,我們符合任何單字字元序列\w+,但前提是它們前面有 字元.。我們可以使用非捕獲組來實現類似的效果,但結果有點混亂:
模式:(?:\.)(\w+)
字串: myArray .flatMap.aggregate.summarise.print!
配對:       ^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^^^
組:            1111111 1111111111 111111111 11111    
示例)儘管它更短,但它匹配我們不需要的字元。雖然這個範例看起來微不足道,但前瞻和後瞻確實可以幫助我們清理正規表示式。<h3>距離終點所剩無幾!以下2 個任務將使我們離它更近一步:</h3> 負向後查找(?<!...) 允許正則表達式引擎繼續嘗試查找匹配項(僅當負向後查找中包含的文本不存在時)顯示直到文字的其餘部分,您需要從中找到匹配項。例如,我們可以使用正規表示式僅來匹配參加會議的女性的姓氏。為此,我們要確保此人的姓氏前面沒有Mr.. 你能為此寫一個正規表示式嗎?(可以假定姓氏至少有四個字元長。)
圖案:
串:先生。布朗女士,史密斯,夫人 瓊斯黛西小姐、先生。綠色的
配對:               ^^^^^ ^^^^^ ^^^^^
群組:                   11111 11111 11111    
解決方案) 假設我們正在清理資料庫,並且有一列代表百分比的資訊。不幸的是,有些人將數字寫為[0.0, 1.0] 範圍內的十進制值,而另一些人則將百分比寫為[0.0%, 100.0%] 範圍內的百分比,還有一些人寫了百分比值,但忘了字面上的百分號%。使用負前瞻(?!...),您可以只標記那些應該是百分比但缺少數字的值嗎%?這些值必須嚴格大於 1.00,但末尾不帶%. (任何數字在小數點前後都不能包含超過兩位數字。)<mark>注意</mark>這個解決方案非常困難。如果你不看我的答案就能解決這個問題,那麼你已經擁有了正規表示式的高超技巧!
圖案:
字串: 0.32 100.00 5.6 0.27 98% 12.2% 1.01 0.99% 0.99 13.13 1.10
匹配:      ^^^^^^ ^^^ ^^^^ ^^^^^ ^^^^
組:         1111111 111 ^^^^^^ ^^^ ^^^^ ^^^^^ ^^^^組: 1111111 111 ^^^^^^ ^^^ ^111111    
) <h2>第 19 步:正規表示式中的條件</h2> RegEx:掌握正規表示式的 20 個簡短步驟。 第 4 - 5 部分我們現在已經到了大多數人將不再使用正規表示式的地步。我們已經涵蓋了大約 95% 的簡單正規表示式用例,並且在步驟 19 和 20 中完成的所有操作通常是由功能更齊全的文字操作語言(如 awk 或 sed)(或通用程式語言)完成的。也就是說,讓我們繼續前進,以便您了解正規表示式的真正作用。儘管正規表示式不是圖靈完備的,但一些正規表示式引擎提供了與完整程式語言非常相似的功能。這樣的特徵之一就是「條件」。正規表示式條件允許 if-then-else 語句,其中所選分支由我們在上一個步驟中了解的「向前看」或「向後看」決定。例如,您可能只想匹配日期清單中的有效條目:
模式:(?<=Feb )([1-2][0-9])|(?<=Mar )([1-2][0-9]|3[0-1])
字串:工作日期: 2月28日, 2月29日, 2月30日 , 3月30日, 3月31日 
比賽:                   ^^ ^^ ^^ ^^
組別:                      11 11 22 22    
範例)<mark>請注意</mark>上述組別也按月索引。我們可以為所有 12 個月編寫一個正規表示式,並僅捕獲有效日期,然後將其組合成按一年中的月份索引的群組。上面使用了一種類似 if 的結構,如果「Feb」在數字前面(第二組也類似),則只會在第一組中尋找匹配項。但是,如果我們只想對二月使用特殊處理怎麼辦?類似於“如果數字前面有“二月”,則執行此操作,否則執行其他操作。” 條件語句的執行方式如下:
模式:(?(?<=Feb )([1-2][0-9])|([1-2][0-9]|3[0-1]))
字串:工作日期:2月28 日, 2月29日, 2月30日 , 3月30日, 3月31日 
比賽:                   ^^ ^^ ^^ ^^
組:                      11 11 22 22    
)if-then-else 結構類似於 (?(If)then|else),其中 (if) 被「向前看」或「向後看」取代。在上面的例子中,(if) 寫成(?<=Feb)。您可以看到我們匹配了大於 29 的日期,但前提是它們不遵循“Feb”。如果您想確保匹配項前面有一些文本,則在條件表達式中使用lookbehinds 非常有用。正向先行條件可能會令人困惑,因為條件本身不符合任何文字。因此,如果您希望 if 條件有一個值,它必須與前瞻相當,如下所示:
模式:(?(?=exact)exact|else)wo
字串:exact else strictwo elsewo 
符合:           ^^^^^^^^ ^^^^^^
)這意味著肯定的先行條件是沒有用的。您檢查該文字是否在前面,然後提供匹配的模式以供遵循。條件表達式在這裡根本沒有幫助我們。您也可以將上面的內容替換為更簡單的正規表示式:
模式:( ?:exact|else)wo
字串:exact else strictwo elsewo 
符合:           ^^^^^^^^ ^^^^^^
範例)因此,條件式的經驗法則是:測試、測試、再測試。否則,您認為顯而易見的解決方案將以最令人興奮和意想不到的方式失敗:) <h3>這裡我們來到了將我們與最後的第20 步分開的最後一個任務塊:</h3>編寫一個正規表示式使用負先行條件表達式來測試下一個單字是否以大寫字母開頭。如果是這樣,請只抓取一個大寫字母,然後抓取小寫字母。如果沒有,請抓取任何單字字元。
圖案:
字串:   Jones Smith 9sfjn Hobbes 23r4tgr9h CSV Csv vVv
符合:^^^^^ ^^^^^ ^^^^^ ^^^^^^ ^^^^^^^^^ ^^^ ^^^
組:    22222 22222 11111 222222 111111111 222 111    
解決方案owns) 編寫一個否定的lookbehind 條件表達式,僅當文本前面沒有文本時才捕獲文本cl,並且ouds僅當文本前面有文本時才捕獲文本cl。(有點人為的例子,但你能做什麼...)
圖案:
字串:那些小丑擁有一些。烏德琴。
配對:             ^^^^ ^^^^   
) <h2>第 20 步:遞迴與進一步研究</h2> RegEx:掌握正規表示式的 20 個簡短步驟。 第 4 - 6 部分事實上,任何主題的 20 步驟介紹都可以壓縮很多內容,正規表示式也不例外。可以在 Internet 上找到許多不同的正規表示式實作和標準。如果您想了解更多信息,我建議您查看精彩的網站regularexpressions.info,這是一個很棒的參考資料,我當然從那裡學到了很多關於正則表達式的知識。我強烈推薦它,以及用於測試和發布您的創作的regex101.com 。在最後一步中,我將為您提供更多有關正規表示式的知識,即如何編寫遞歸表達式。簡單的遞歸非常簡單,但讓我們考慮一下這在正規表示式的上下文中意味著什麼。正規表示式中簡單遞歸的語法如下所示:(?R)?。但是,當然,此語法必須出現在表達式本身。我們要做的是將表達式嵌套在其自身中,任意次數。例如:
模式:(hey(?R)?oh)
字串:   heyoh heyyoh heyheyohoh hey oh heyhey hey heyheyohoh 
符合:^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^
組:    11111 1111111111 1111111111    
範例)由於巢狀表達式是可選的((?R)後跟?),最簡單的匹配就是完全忽略遞歸。因此,hey,然後oh匹配 ( heyoh)。要匹配任何比這更複雜的表達式,我們必須在表達式中插入序列的位置找到嵌套在其自身內部的匹配子字串(?R)。換句話說,我們可以找到 heyheyohoh 或 heyheyheyohohoh,等等。這些嵌套表達式的優點之一是,與反向引用和命名捕獲組不同,它們不會將您限制為先前逐個字元匹配的確切文字。例如:
模式:([Hh][Ee][Yy](?R)?oh)
字串:   heyoh heyyoh hEyHeYohoh hey oh heyhey hEyHeYHEyohohoh 
符合:^^^^^ ^^^^^^^^^^ ^^^^ ^ ^^^^^^^^^^^
群:    11111 1111111111 111111111111111    
範例)您可以想像正規表示式引擎實際上將正規表示式複製並貼上到自身中任意次。當然,這意味著有時它可能不會達到您所希望的效果:
模式:((?:\(\*)[^*)]*(?R)?(?:\*\)))
字串:(* 註解(* 巢狀 *)不是 *)
配對:           ^^^^^^^^^^^^^
群組:               111111111111    
範例)你能說出為什麼這個正規表示式只捕獲嵌套註解而不捕獲外部註解嗎?有一點是肯定的:在編寫複雜的正規表示式時,請務必對其進行測試,以確保它們按照您認為的方式運作。這場正規表示式之路上的高速拉力賽已經結束了。我希望你喜歡這趟旅程。好吧,最後,正如我在一開始所承諾的那樣,我將在這裡留下幾個有用的鏈接,以便更深入地研究該材料:
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION