正規

正規表現 の jq

1個の演習

正規表現について

正規表現(regex)は、テキストの中から検索パターンを指定する文字の並びです。

正規表現の構文を学ぶことは、このトピックの範囲を超えています。ここでは、jqが正規表現を扱うために用意している表現に焦点を当てます。

正規表現の方言

ツールによって、実装されている正規表現のバージョンが異なります。jqは、Perl v5.8の正規表現とほぼ互換性のあるOnigurumaという正規表現ライブラリを取り込んでいます。

jqが使う具体的な構文は、OnigurumaのGitHubリポジトリで確認できます。

Caution

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)
Caution

引数を1つだけ取るsplitフィルターは、引数を固定文字列として扱います。

splitで正規表現を使うには、2番目の引数を必ず指定してください。空文字列でもかまいません。

任意の空白文字で文字列を分割する例です。

"first   second           third fourth" | split("\\s+"; "")
# => ["first", "second", "third", "fourth"]
Note

フラグの引数を忘れると、次のようになります。

"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")
GitHubで編集 リンクは新しいウィンドウまたはタブで開きます

正規表現を学習する

練習はロックされています

正規表現を練習するには、あと1個の演習のロックを解除してください