正規表達式(regex)是一連串的字元,用來描述文字中的搜尋模式。
學習正規表達式的語法已超出本主題的範圍。
我們會把重點放在jq提供的篩選器,看看如何運用正規表達式。
不同的工具會實作不同版本的正規表達式。
jq內建 Oniguruma 正規表達式函式庫,它大致上與 Perl v5.8 的正規表達式相容。
jq所使用的具體語法可以在 Oniguruma 的 GitHub 存放庫 找到。
jq沒有針對正規表達式的特殊語法。
它們就是單純以字串表示。
這代表正規表達式中的任何反斜線,都必須在字串中跳脫。
例如,數字字元類別(\d)必須寫成 "\\d"。
jq中的正規表達式僅限於一組篩選器。
當你需要知道某個字串是否符合某個模式時,請使用test篩選器。
STRING | test(REGEX)
STRING | test(REGEX; FLAGS)
STRING | test([REGEX, FLAGS])
這個篩選器會輸出_布林值_的結果。
"Hello World!" | test("W") # => true
"Goodbye Mars" | test("W") # => false
當你需要擷取真正符合模式的子字串時,請使用match篩選器。
STRING | match(REGEX)
STRING | match(REGEX; FLAGS)
STRING | match([REGEX, FLAGS])
這個篩選器會輸出:
這個範例使用反向參照語法\1,尋找兩個相同且連續的母音。
"Hello World!" | match("([aeiou])\\1")
# => empty
"Goodbye Mars" | match("([aeiou])\\1")
# => {
# "offset": 1,
# "length": 2,
# "string": "oo",
# "captures": [
# {
# "offset": 1,
# "length": 1,
# "string": "o",
# "name": null
# }
# ]
# }
match篩選器會為_每個_相符結果回傳一個物件。
這個範例展示了"g"旗標的實際運作,用來找出所有的母音。
"Goodbye Mars" | match("[aeiou]"; "g")
# => { "offset": 1, "length": 1, "string": "o", "captures": [] }
# { "offset": 2, "length": 1, "string": "o", "captures": [] }
# { "offset": 6, "length": 1, "string": "e", "captures": [] }
# { "offset": 9, "length": 1, "string": "a", "captures": [] }
與match篩選器類似,capture篩選器在找到相符結果時會回傳一個物件。
STRING | capture(REGEX)
STRING | capture(REGEX; FLAGS)
STRING | capture([REGEX, FLAGS])
回傳的物件是具名擷取的對應。
"JIRAISSUE-1234" | capture("(?<project>\\w+)-(?<issue_num>\\d+)")
# => {
# "project": "JIRAISSUE",
# "issue_num": "1234"
# }
scan篩選器類似於帶有"g"旗標的match。
STRING | scan(REGEX)
STRING | scan(REGEX; FLAGS)
scan會輸出子字串的_串流_。
"Goodbye Mars" | scan("[aeiou]")
# => "o"
# "o"
# "e"
# "a"
使用[...]陣列建構子來收集這些子字串。
"Goodbye Mars" | [ scan("[aeiou]") ]
# => ["o", "o", "e", "a"]
如果你知道字串中想要保留的部分,請使用match或scan。
如果你知道想要捨棄的部分,請使用split。
STRING | split(REGEX; FLAGS)
只接收一個引數的split篩選器會把引數視為固定字串。
如果要讓split使用正規表達式,你必須提供第二個引數;使用空字串也沒關係。
以下範例會依任意空白切割字串。
"first second third fourth" | split("\\s+"; "")
# => ["first", "second", "third", "fourth"]
sub與gsub篩選器可以轉換輸入字串,將輸入中相符的部分取代為取代字串。
如果只想取代第一個相符結果,請使用sub。
如果要取代所有相符結果,請使用gsub。
STRING | sub(REGEX; REPLACEMENT)
STRING | sub(REGEX; REPLACEMENT; FLAGS)
STRING | gsub(REGEX; REPLACEMENT)
STRING | gsub(REGEX; REPLACEMENT; FLAGS)
"Goodnight kittens. Goodnight mittens." | sub("night"; " morning")
# => "Good morning kittens. Goodnight mittens."
"Goodnight kittens. Goodnight mittens." | gsub("night"; " morning")
# => "Good morning kittens. Good morning mittens."
取代的文字可以參照相符的子字串;請使用具名擷取與字串插值。
"Some 3-letter acronyms: gnu, csv, png"
| gsub( "\\b(?<tla>[[:alpha:]]{3})\\b"; # find words 3 letters long
"\(.tla | ascii_upcase)" ) # upper-case the match
# => "Some 3-letter acronyms: GNU, CSV, PNG"
在上述所有篩選器中,FLAGS 是一個由零個或多個支援旗標組成的字串。
g:全域搜尋(找出所有相符結果,不只第一個)i:忽略大小寫的搜尋m:多行模式('.' 會符合換行)n:忽略空相符p:同時啟用 s 與 m 模式s:單行模式('^' -> '\A', '$' -> '\Z')l:找出可能的最長相符結果x:擴充的正規表達式格式(忽略空白與註解)例如
"JIRAISSUE-1234" | capture("(?<project>\\w+)-(?<issue_num>\\d+)")
# or with Extended formatting
"JIRAISSUE-1234" | capture("
(?<project> \\w+ ) # the Jira project
- # followed by a hyphen
(?<issue_num> \\d+ ) # followed by digits
"; "x")
你受聘到一家正在開發 Chatbot 的公司,擔任正規表示式專家。
這項開發還處於非常初期的階段。 你的任務是運用正規表示式,提升 Chatbot 理解與產生自然語言的能力。
Chatbot 不只聰明,還是個忠心的助手。 使用者必須把「Chatbot」這個詞放在指令的第一個位置,才能向 Chatbot 提問。 關鍵字是大寫還是小寫都沒關係。 重要的是這個詞出現的位置。
實作 is_valid_command 函式,協助 Chatbot 辨識使用者是否正在下指令。
"Chatbot, play a song from the 80's." | is_valid_command
# => true
"Hey Chatbot, where is the closest pharmacy?" | is_valid_command
# => false
"CHATBOT, do you have a solution for this challenge?" | is_valid_command
# => true
Chatbot 很難理解人類如何用表情符號表達情緒。 Chatbot 收到使用者訊息時,每個表情符號都會以字串「emoji」加上一個 id 編號 來表示。
實作 remove_emoji 方法,它接受一個字串,並移除訊息中所有的表情符號。
不含表情符號的行應原樣回傳。 只要移除表情符號字串就好。 不要調整空白。
"I love playing videogames emoji3465 it's one of my hobbies" | remove_emoji
# => "I love playing videogames it's one of my hobbies"
在與 Chatbot 互動的某個時刻,使用者會提供電話號碼。 Chatbot 只能撥打特定格式的號碼。
實作 check_phone_number 函式。
如果號碼有效,Chatbot 會回覆一則訊息,感謝使用者並確認號碼。 如果號碼無效,Chatbot 會告知使用者該電話號碼無效。
預期的格式是 (+NN) NNN-NNN-NNN,其中 N 是數字。
"chatbot my phone number is (+34) 659-771-594" | check_phone_number
# => "Thanks! Your phone number is OK."
"chatbot, call me at 659-771-594" | check_phone_number
# => "Oops, it seems like I can't reach out to 659-771-594."
Chatbot 是一套非常好奇的軟體。 雖然它能上網搜尋特定主題,但它喜歡詢問使用者有哪些很酷的網站可以造訪,以找到相關資訊。
對話範例:
Chatbot:嘿,使用者,我想學怎麼用 JavaScript 寫程式。你知道有什麼很酷的網站可以讓我學習嗎?
使用者:我從 exercism.org 學到很多,那裡有很多很棒的資源。
實作 get_domains 函式,它會回傳網站網域的陣列。
"I learned a lot from exercism.org and google.com" | get_domains
# => ["exercism.org", "google.com"]
有禮貌的 Chatbot 會用名字稱呼使用者。 當使用者自我介紹時,我們的 Chatbot 會偵測他們的名字,並以親切的問候回應。
撰寫 nice_to_meet_you 函式。
如果輸入字串包含「My name is Someone.」,請擷取名字並回傳字串「Nice to meet you, Someone.」。
"My name is Jean-Luc" | nice_to_meet_you
# => "Nice to meet you, Jean-Luc"
抵擋不住「功能蔓延」的誘惑,我們要在 Chatbot 裡加一個 CSV 解析函式。
實作 parse_csv 函式,它接受一個字串,並回傳解析出的欄位陣列。
欄位分隔符號應該是「逗號加上選擇性的空白」。
我們不用擔心 CSV 格式的任何邊界情況(例如欄位中含有逗號)。
"first, second,third, fourth" | parse_csv
# => ["first", "second", "third", "fourth"]