十五分鐘認識正規表達式,解決所有文字難題

文章推薦指數: 80 %
投票人數:10人

正規表達式(Regular Expression),是一種用來描述字串 符合某個語法規則 的模型(pattern),可以用來做文字的搜尋、比對、萃取、替代、轉換等等,在許多的 ... 首頁技術文章十五分鐘認識正規表達式,解決所有文字難題十五分鐘認識正規表達式,解決所有文字難題ByJerry・7月142020・技術文章前言在軟體工作中,經常要對文字做處理,例如資料的搜尋、擷取並重組文字、驗證使用者輸入等等,遇到這類與字串有關的問題,依情況使用正規表達式可以免去很多處理上的麻煩,使程式碼更簡單好懂。

這篇文章將帶你用15分鐘的時間,透過MDN文檔與幾個常見的例子,帶你初步認識並使用這門技術,我們話不多說直接開始吧!什麼是正規表達式?正規表達式(RegularExpression),是一種用來描述字串符合某個語法規則的模型(pattern),可以用來做文字的搜尋、比對、萃取、替代、轉換等等,在許多的程式語言中都支援正規表達式的使用,以下範例將以Javascript為例。

撰寫正規表達式撰寫正規表達式時,使用兩個斜線//或是newRegExp()來建立一個RegExp物件。

//1.使用literal,這種方式會在script載入時就被編譯,效能較好。

constregex=/sometext/ //2.使用new建構一個RegExp物件,適合用在需要動態產生pattern的場合。

constregex=newRegExp('sometext') //加上flag設定,使比對的能力更強大。

i:不區分大小寫,g:比對字串所有位置 constregex=/sometext/i constregex=newRegExp('sometext','g')使用正規表達式建立了正規表達式後,就可以使用RegExp物件中test以及exec來對字串做處理囉。

constregex=/helloworld/i //使用test比對字串是否符合pattern,回傳boolean regex.test('HelloWorld!!')//true //使用exec取得比對的詳細資訊,比對失敗時回傳null regex.exec('HelloWorld!!')//["HelloWorld",index:0,input:"HelloWorld!!",groups:undefined] regex.exec('HelloRegex!!')//null在String物件中的search、match、replace、split等方法中,也有支援正規表達式寫法。

constparagraph='LoremIpsumissimplydummytextoftheprintingandtypesettingindustry.' //使用search搜尋字串是否在段落中,有找到回傳字串的起始位置,沒找到回傳-1 paragraph.search('tExT')//-1 paragraph.search(/tExT/i)//28 //使用match找出第一個比對成功的詳細資訊,加上gflag則會列出所有比對成功的字串 paragraph.match(/ing/)//["ing",index:45,...] paragraph.match(/ing/g)//["ing","ing"]特殊字元在正規表達式中,某些特定的字元或符號屬於保留字,直接使用可能與預期的效果不同。

conststr='RailsisawebframeworkwritteninRuby' //^表示pattern必須在字串的開頭 str.match(/^Rails/)//["Rails",index:0,...] str.match(/^Ruby/)//null //$表示pattern必須在字串的結尾 str.match(/Ruby$/)//["Ruby",index:36,...] str.match(/Rails$/)//null //|表示或(or),|前後的字串都可以比對 constregex=/color|colour/ regex.exec('color')//["color",index:0,...] regex.exec('colour')//["colour",index:0,...] //當要比對這些特殊符號時,使用反斜線'\'來跳脫特殊字元 constregex=/\$100/ regex.test('$100')//true集合[]在前面的例子中,pattern都有指定明確的文字,如果想要比對的是英文、數字或是幾種特定的組合,就可以使用集合[]來將它們一網打盡,集合代表著這一個字元可以是[]內的其中一種。

//只要是英文大寫字母,就比對成功 constregex=/[ABCDEFGHIJKLMNOPQRSTUVWXYZ]/ 'K'.match(regex)//["K",index:0,...] 'δ'.match(regex)//null //可以使用'-'來簡化集合,'A-Z'表示英文字母A~Z都符合 constregex=/[A-Z]/ //若要比對的是英文或數字,可以這樣表示 constregex=/[A-Za-z0-9]/一些常用的集合有對應的特殊字元。

constregex=/.///比對換行符號外的任意一個字元 constregex=/\d///比對一個數字,相等於/[0-9]/ constregex=/\w///比對一個英文、數字或底線,相等於/[A-Za-z0-9_]/ constregex=/\s///比對一個的空格(ex:space,tab,換行,...)使用排除法[^]來比對這個集合以外的字元constregex=/[^\w]/ regex.test('a')//false regex.test('!')//true量詞{}在集合的內容中我們提到,使用集合一次也只能比對一個文字,此時,若我們想比對連續的相同規則時,可以使用量詞{}來修飾。

//不使用量詞時,要比對5個連續的數字就必須寫5次 constregex=/\d\d\d\d\d/ regex.test('12345')//true //使用{5}表示連續出現5次 constregex=/\d{5}/ regex.exec('abcde12345')//["12345",index:5,...] regex.exec('a1b2c3d4e5')//null //使用{2,}表示連續出現2次以上 constregex=/\w\+{2,}/ regex.exec('a+')//null regex.exec('a++')//["a++",index:0,...] //使用{2,5}表示連續出現2~5次 constregex=/^\w{2,5}!/ regex.exec('Hi!')//["Hi!",index:0,...] regex.exec('Helloooo!')//null量詞也有特殊字元可以替代。

//使用?表示出現0或1次,等同於{0,1} constregex=/\w?/ //使用+表示出現1次或以上,等同於{1,} constregex=/\w+/ //使用*表示出現0次或以上,等同於{0,} constregex=/\w*/使用上,+、?、*、{2,5}都是屬於Greedy量詞,意思是會以連續出現次數越多為優先,相反的,在量詞後面加上一個問號+?、??、*?、{2,5}?就變成Lazy量詞,意思是以連續出現次數越少為優先。

//'+'出現的次數越多優先 constregex=/a\+{2,}/ regex.exec('a+++++')//["a+++++",index:0,...] //'+'出現的次數越少優先 constregex=/a\+{2,}?/ regex.exec('a+++++')//["a++",index:0,...]練習1看到這裡,已經可以使用正規表達式來做一些處理了,讓我們來練習一下。

1使用正規表達式來檢驗日期格式是否正確。

//假設正確的日期格式為'2020/07/15' //分析一下是由"4個數字/2個數字/2個數字"所組成, //把這個規則寫成正規表達式 constregex=/\d{4}\/\d{2}\/\d{2}/ //若要支援'2020/07/15'、'2020-07-15'兩種寫法,可以使用集合來處理 constregex=/\d{4}[/\-]\d{2}[/\-]\d{2}/ regex.test('2020/07/15')//true regex.test('2020-07-15')//true2使用正規表達式解析excel複製來的資料。

//假設使用者在excel選取了'sn0001'、'sn0002'、'sn0003'三個欄位 //分析一下我們要的每一筆資料是由"連續的英文或數字"所組成 //把這個規則寫成正規表達式 constregex=/[A-Za-z0-9]+/ //在複製文字的時候會發現,除了流水號之外,分隔、換行符號也一起被複製了進來 //用replace去trim掉不需要的字元在處理上比較麻煩 //改使用match一次取出所有資料,並轉成陣列供後續使用 constcopiedData=` sn001, sn002, sn003 ` copiedData.match(/[A-Za-z0-9]+/g)//["sn001","sn002","sn003"]3使用正規表達式找出被**包起來的片段。

constparagraph="LoremIpsumissimplydummytextofthe*printing*and*typesetting*industry." //分析一下我們要的資訊是由'*'+任意內容+'*'所組成 //把這個規則寫成正規表達式 constregex=/\*.*\*/g paragraph.match(regex)//["*printing*and*typesetting*"] //執行之後會發現結果不如預期 //原因在於中間的任意內容我們是使用Greedy量詞'*'去比對,也就是任意字元且越多越好 //改使用Lazy量詞就能獲得想要的結果 constregex=/\*.*?\*/g paragraph.match(regex)//["*printing*","*typesetting*"]這裡小結一下,在上面的內容中,我們已經學會使用正規表達式來做簡單的搜尋與比對了,如果我想要把日期2020/07/15轉換成2020年07月15日,或是要將數字123456789加上三位一撇123,456,789該怎麼做呢?下面我們將繼續介紹正規表達式中的群組以及斷言功能。

群組(CapturingGroup)使用正規表達式時,除了比對文字外,也可以透過使用Group來捕獲特定的文字,被捕獲的文字會出現在比對的結果中,提供後續的程式使用。

//在使用exec、match等方法時,回傳的結果會有以下這些資訊 //[比對成功的字串,捕獲的字串1,捕獲的字串2,...,字串的起始位置,命名的捕獲群組] constregex=/user:(\w+)/ regex.exec('user:Alan')//["user:Alan","Alan",index:0,...] //每一個Group會'由左至右,由外而內'的被賦予編號,並被放在執行結果相應的index中 //假設regex=/((A)(B))/,那麼群組就分別是1:(AB),2:(A),3:(B) constregex=/fullName:((\w+)(\w+))/ regex.exec('fullName:AlanHsu')//["fullName:AlanHsu","AlanHsu","Alan","Hsu",...]如果想要比對的是重複出現的內容,可以使用反向參考backreference來代替已被捕獲的文字。

//假設想找出猜數字中a和b出現次數相同的回合 //作法1→我們可以先把a與b的次數Group起來,再做判斷 constregex=/(\d+)a(\d+)b/ constresult=regex.exec('1a1b')//["1a1b","1","1",...] if(result[1]===result[2])... //作法2→使用backreference,讓\1、\2、...替換成被捕獲的數字 constregex=/(\d+)a\1b/ regex.test('1a1b')//true regex.test('1a2b')//false捕獲的文字在使用上需要透過計算群組的編號來取得,反應在程式碼上並不是那麼語意化,這時我們可以改使用命名群組(?)來替群組命名,反向參考的用法改為\k

//假設要抓出使用者的firstName與lastName constregex=/fullName:(?\w+)(?\w+)/ regex.exec('fullName:AlanHsu')//[...,groups:{firstName:"Alan",lastName:"Hsu"}]群組也能搭配|或是量詞一起使用。

//使用群組簡化|的寫法 constregex=/goodapple|badapple/ constregex=/(good|bad)apple/ //使用量詞簡化重複的規則 constregex=/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ constregex=/(\d{1,3}\.){3}\d{1,3}/ regex.exec('192.168.0.1')//["192.168.0.1","0.",index:0,...]如果捕獲的文字不是那麼重要,則可以改使用non-capturinggroup(?:)來撰寫。

//使用non-capturinggroup,(?:)內的文字就不會被捕獲,也就無法使用backreference constregex=/(?:\d{1,3}\.){3}\d{1,3}/ regex.exec('192.168.0.1')//["192.168.0.1",index:0,...]斷言(Assertions)最後一個要介紹的主題是斷言,根據維基百科的解釋,斷言是一種放在程式中的一階邏輯(例如一個結果為true或false的判斷式),當程式執行到斷言的位置時就會執行判斷,若結果為true則繼續執行,結果為false則中止執行。

在正規表達式中,斷言可以用來指定字串中的某個錨點要符合一些條件,例如前面介紹的特殊字元在字串開頭^、在字串的結尾$就是被歸類在斷言的用法中,常見的斷言還有文字邊界\b與環顧Lookaround。

//假設要找出'Java'而不是'Javascript' conststr='differencebetweenJavascriptandJava.' //不使用斷言會找到'Javascript'的'Java' str.match(/Java/)//["Java",index:19,...] //改使用文字邊界\b來比對,就能找到想要的結果了 str.match(/\bJava\b/)//["Java",index:34,...] //文字邊界指的是在比對到\b的位置時,前後相鄰的字元必須有一個不是文字 //使用replace把\b替換成'|'來看看它的效果 constregex=/\b/g str.replace(regex,'|')//"|difference||between||Javascript||and||Java|."字串與錨點的描述,建議到regex101實際操作一下,配合網站的視覺效果可以更好的理解它,如果想要判斷的是更複雜的條件,可以使用環顧Lookaround。

//Lookaround分為兩種`Lookahead`以及`Lookbehind`,各自又有positive與negative兩種判斷方式 //PositiveLookahead:A(?=B)→A後方的條件要符合B //NegativeLookahead:A(?!B)→A後方的條件不能符合B //PositiveLookbehind:(?<=A)B→B前方的條件要符合A //NegativeLookbehind:(?替代命名群組捕獲的文字 constregex=/(?\d{4})\/(?\d{2})\/(?\d{2})/ date.replace(regex,'$年$月$日')//"2020年07月15日"2使用正規表達式幫數字加上三位一撇(separator)。

其實可以直接用toLocaleString()constnumber=123456789//123,456,789 //分析一下條件,從個位數開始數,每三位數加上一個',' //使用非文字邊界\B與Lookahead來撰寫條件,判斷右邊每3個數字為錨點 constregex=/\B(?=(?:\d{3})+)/g number.toString().replace(regex,',')//"1,2,3,4,5,6,789" //發現結果不如預期,從數字5開始往右數三個數字也符合條件 //在Lookahead裡再加上一個NegativeLookahead //判斷每湊滿3、6、9、...個數字之後,右邊不能再有其他數字 constregex=/\B(?=(?:\d{3})+(?!\d))/g number.toString().replace(regex,',')//"123,456,789"3使用正規表達式限制使用者只能輸入英文與數字//分析一下條件,每個字元都只能是英文與數字 //反過來說就是要trim掉不是英文與數字的字元 constvalue=e.target.value this.value=value.replace(/[^A-Za-z0-9]/g,'')總結正規表達式這項技術除了在web開發上常被使用之外,在爬蟲、數據分析等應用也用得上,本期文章使用十五分鐘的時間帶大家初步認識正規表達式的用法,更多的內容例如比對的優先順序、多重條件的撰寫、正規表達式的效能等主題可以透過MDN文檔、網路搜尋或是相關的書籍來做進一步的學習,以上就是這次的全部內容,若有錯誤或需要補充的部分還請不吝指出,感謝你的收看:)參考資料正規表達式-JavaScript|MDNregex101斷言-WikiNumberwithcommas-StackOverflowGoogle👩‍🏫課務小幫手:✨想掌握JavaScript觀念和原理嗎?我們有開設📒JavaScript/jQuery前端開發入門實戰課程唷❤️️訂閱文章訂閱我們電話|(02)2331-5247信箱|[email protected]地址|10046台北市中正區衡陽路7號5樓專業課程ASTROCamp線上課程短期課程關於我們關於五倍紅寶石聯絡我們媒體報導加入我們部落格技術文章五倍消息幫助與政策常見問題隱私權政策服務條款©2014-2022五倍紅寶石程式資訊教育股份有限公司(5xRubyTrainingGroupCO.,LTD)|台北市短期補習班立案第7594號



請為這篇文章評分?