正規表現(regex)は、テキストの中から検索パターンを指定する文字の並びです。
正規表現の構文を学ぶことは、このトピックの範囲を超えています。ここでは、jqが正規表現を扱うために用意している表現に焦点を当てます。
ツールによって、実装されている正規表現のバージョンが異なります。jqは、Perl v5.8の正規表現とほぼ互換性のあるOnigurumaという正規表現ライブラリを取り込んでいます。
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を使って、同じ母音が2つ連続する箇所を探します。
"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)
# 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)
引数を1つだけ取るsplitフィルターは、引数を固定文字列として扱います。
splitで正規表現を使うには、2番目の引数を必ず指定してください。空文字列でもかまいません。
任意の空白文字で文字列を分割する例です。
"first second third fourth" | split("\\s+"; "")
# => ["first", "second", "third", "fourth"]
フラグの引数を忘れると、次のようになります。
"first second third fourth" | split("\\s+")
# => ["first second third fourth"]
結果は1つだけです。入力の中に_固定文字列_の\s+が見つからなかったからです。
引数を1つだけ取る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は、対応しているフラグを0個以上並べた文字列です。
g:全体検索(最初の1つだけでなく、すべてのマッチを探す)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")