Регулярні вирази (regex) - це послідовності символів, які задають шаблон пошуку в тексті.
Вивчення синтаксису регулярних виразів виходить за межі цієї теми.
Ми зосередимося на виразах, які jq надає для роботи з регулярними виразами.
Різні інструменти реалізують різні версії регулярних виразів.
jq використовує бібліотеку регулярних виразів Oniguruma, яка значною мірою сумісна з регулярними виразами Perl v5.8.
Конкретний синтаксис, який використовує jq, можна знайти в репозиторії Oniguruma на GitHub.
У jq немає якогось спеціального синтаксису для регулярних виразів.
Їх просто записують як рядки тексту (англ. string).
Це означає, що будь-які зворотні косі риски в регулярному виразі потрібно екранувати в рядку.
Наприклад, клас символів для цифр (\d) потрібно записати як "\\d".
Робота з регулярними виразами в jq зводиться до набору фільтрів.
Коли потрібно дізнатися, чи рядок тексту відповідає шаблону, скористаймося фільтром test.
STRING | test(REGEX)
STRING | test(REGEX; FLAGS)
STRING | test([REGEX, FLAGS])
Цей фільтр видає булеве значення (англ. Boolean).
"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 подібний до match із прапорцем "g".
STRING | scan(REGEX)
STRING | scan(REGEX; FLAGS)
# note, there is no scan([REGEX, FLAGS]) version, unlike other filters
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"]
Ось що станеться, якщо забути про аргумент із прапорцями.
"first second third fourth" | split("\\s+")
# => ["first second third fourth"]
Лише один результат: фіксованого рядка тексту \s+ у вхідних даних не було.
split з одним аргументом не впорається з довільними пробільними символами.
Розбиття за пробілом дає такий результат.
"first second third fourth" | split(" ")
# => ["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")