軽量JSONパーサー『jq』のドキュメント:『jq Manual』をざっくり日本語訳してみました

jq
742件のシェア(とっても話題の記事)

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

jq

JSON形式の情報を様々な条件や書式として成形、フィルタリングツール『jq』。上記関連エントリで私もこのツールの存在を知る事になったのですが、ツールの簡易さ・便利さに感動しながら私もちょくちょく利用させてもらっています。

そこでこのエントリでは、jq公式ページに展開されている利用ガイド・リファレンス的な位置付けの『jq Manual』を写経がてらざっくり日本語訳してみました。ざっくり訳なのでこの部分の訳おかしい・間違ってる等ありましたら御指摘頂けると幸いです。例示されているサンプルコードも併せてコマンドラインで試してみています。


jq Manual(jq利用マニュアル)

jqはフィルタプログラムです。入力を受け取り、出力を生成します。

jqはオブジェクトから一部分を抽出したり、数値を文字列に変換したり、その他様々な用途に応じた組み込みのフィルタを持っています。また、フィルタは様々な方法で連結可能です。ある出力をパイプ(|)で繋いで後続に渡したり、配列からフィルタを通じて出力を抽出したりする事が出来ます。

幾つかのフィルタは復数の結果を返す事があります。例えば、入力配列の全ての要素を生成するものとか。2つ目のフィルタをパイプで繋ぐと、配列要素全てに対してその2つ目のフィルタを実行します。一般的に、その他の言語でループや反復で行われるであろう物事については、jq内では一緒に接着フィルタ(gluing filter)として実行されます。

全てのフィルタは入力と出力を持っていると言うことは大事なので覚えておいてください。"hello"や"42"のような文字列でさえ、フィルタです - それらは入力を受け取りますが、常に出力として同じ文字列を生成します。

また、2つのフィルタを連結した場合、一般的には同じ入力を受け取り、それらの結果を結合します。なので、あなたは平均化するフィルタ - addフィルタとlengthフィルタ双方に入力として配列を渡し、それらの結果を分割するような - を実装する事が出来ます。

まずはシンプルなものから始めてみましょう!

jqを呼び出す

jqのフィルタはjsonデータストリーム上で動きます。

jqへの入力は、提供されたフィルタを通っている、空白で区切られたjson形式の値の一連の要素として解析されます。

フィルタの出力は、空白で区切られた一連のjsonデータの結果として再び標準出力に書き込まれます。それら入出力の読み書きはコマンドラインオプションでカスタマイズする事が出来ます。

--slurp / -s

入力のJSONオブジェクト毎にフィルタを実行する代わりに、大きな配列に入力ストリームを読み込ませ、 一度だけフィルタを実行するようにします。

--raw-input / -R

入力値は、JSONとしてパースしないでください。その代わりに、(このオプションを用いる事で)テキストの各行は文字列としてフィルタに渡されます。もし --slurpオプションと組み合わせた場合、入力全体が単一の長い文字列として フィルタに渡されます。

--null-input / -n

どんな値でも読み込もうとしないでください!その代わりに(このオプションを用いる事で)入力値としてnullを扱うようにします。これはシンプルな電卓として、或いはゼロからJSONを構築する為にjqを使用する場合に便利です。

--compact-output / -c

デフォルト設定では、jqは出力を良い感じに整形して出力してくれます。このオプションを用いると、JSONオブジェクトの値それぞれを1行に詰め込んだ、よりコンパクトな出力結果となります。

--colour-output / -C and --monochrome-output / -M

コンソールを用いる場合、デフォルトではjqはカラーリングされたJSONの結果を出力します。

--colour-output または -Cオプションを用いることでパイプ若しくはファイルに書き込む場合でもカラーリングを強制する事が、--monochrome-output または -Mオプションを用いることでカラーリングを無効化する事が出来ます。

--ascii-output / -a

jqはアスキーコード以外の文字列は常にUTF-8で出力します。例えそれらが("\ u03bc"のような)エスケープシーケンスであったとしても。 このオプションを利用すると、jqに対して純粋なascii出力を行う(全ての非ASCII文字を等価なエスケープシーケンスで置き換える)ように強制する事が出来ます。

--raw-output / -r

このオプションでは、もしフィルタの結果が文字列だった場合、引用符付きのJSON文字列でフォーマットされると言うよりもむしろ それらは直接標準出力に対して書き込まれます。これは、非JSONベースのシステムと連携するようなjqのフィルタを作成するのに役に立つでしょう。

--arg name value

このオプションは、予め定義された変数として、jqプログラムに値を渡します。『--arg foo bar』と実行した場合、$foo はプログラム内で変数となり、"bar"という値を持つ事になります。

基本的なフィルタ

.

最もシンプルなフィルタ。入力結果を何も変えずに出力します。jqはデフォルトで出力結果を整形するので、この些細なプログラムはsayやcurlからのJSON出力を整形表示する便利な方法となるでしょう。

$ echo \"Hello, world\" | jq '.'
"Hello, world"

.foo

辞書やハッシュの様なJSONオブジェクトが渡された時に、.(ドット)の後に続けた文字列のキーに該当する値を返します。もしそのキーが存在しない場合はnullを返します。

$ echo '{"foo": 42, "bar": "less interesting data"}' | jq '.foo'
42
$ echo '{"notfoo": true, "alsonotfoo": false}' | jq '.foo'
null

.[foo], .[2], .[10:15]

.["foo"]のような形式でオブジェクトのフィールドを参照する事が出来ます。(.fooは上記バージョンのより簡略化した記法)これはキーが数字だった場合、配列のように作用します。Javascript同様、配列のインデックスは0始まりとなっているので、.[2]と指定した場合は先頭から3つ目の要素を返す事になります。

.[10:15]という記法は配列内の部分配列を取得する際に使えます。この記法によって返される配列の長さは5。『10以上15未満』で示される値の配列(10,11,12,13,14)になります。

(配列の終わりから逆方向にカウントする場合)インデックスが負の値と成るかもしれないし、また(配列の開始または終了を指す場合)その値は省略されるかもしれません。

$ echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[0]'
{
  "good": true,
  "name": "JSON"
}
$ echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[2]'
null
$ echo '["a","b","c","d","e"]' | jq '.[2:4]'
[
  "c",
  "d"
]
$ echo '["a","b","c","d","e"]' | jq '.[:3]'
[
  "a",
  "b",
  "c"
]
$ echo '["a","b","c","d","e"]' | jq '.[-2:]'
[
  "d",
  "e"
]

[]

.[foo]の記法を用いる際に[]内の要素を省略した場合、配列の要素全てを返します。

入力値の配列[1,2,3]に対して.[]を実行した場合、単一の配列としてではなく、3つの別々の結果としての数字を生成します。

また、オブジェクトに対してもこれを使用することができます、その場合はオブジェクトのすべての値を返します。

$ echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[]'
{
  "good": true,
  "name": "JSON"
}
{
  "good": false,
  "name": "XML"
}
$ echo '[]' | jq '.[]'

$ echo '{"a": 1, "b": 1}' | jq '.[]'
1
1

,

2つのフィルタがカンマで連結されていた場合、入力は2つのフィルタそれぞれに渡され、各々のフィルタリング結果が連結して出力されます。

例えば、フィルタ .foo, .bar は別々に.fooフィルタと.barフィルタの結果を出力します。

$ echo '{"foo": 42, "bar": "something else", "baz": true}' | jq '.foo, .bar'
42
"something else"
$ echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq '.user, .projects[]'
"stedolan"
"jq"
"wikiflow"
$ echo '["a","b","c","d","e"]' | jq '.[4,2]'
"e"
"c"

|

基本的に殆どUnixシェルに於けるパイプと同じです。左側の出力結果を右側の入力パラメータとして利用します。

$ echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[] | .name'
"JSON"
"XML"

型と値

jqはJSON同様、以下のデータ型をサポートしています。

  • 数値
  • 文字列
  • ブール型
  • 配列
  • オブジェクト(ここでは文字列キーを持つハッシュの意)
  • "null"

ブール値、null、文字列、数値はJavascriptの場合と同じように書かれています。ただ、jqに於いては他の全てと同様に、これらのシンプルな値は入力を扱い、出力を生成します。42はその入力を扱い、(フィルタリングを)無視し、代わりに42を返す有効なjq式となります。

配列 - []

JSONでは、[][1,2,3]のように配列を構築する際に使われます。配列の要素は、任意のJQ式にすることができます。 全ての処理の結果は1つの大きな配列に集約されます。

[.foo, .bar, .baz]のように配列を作成する事も出来るし、[.items[].name]のように配列をフィルタリングする際に利用する事も出来ます。

一度,演算子を理解してしまえば、別の角度からjqの配列を見ることが出来るでしょう。[1,2,3]はカンマで区切られた配列に対して、ビルトインの構文を使ってはいません。代わりに3つの異なる結果1,2,3に[]演算子(結果を収集する)を適用しています。

もし4つの結果を生成するフィルタXを持っている場合、式[X]は単一の結果、4つの要素の配列を生成します。

$ echo '{"user":"stedolan", "projects": ["jq", "wikiflow"]}' | jq '[.user, .projects[]]'
[
  "stedolan",
  "jq",
  "wikiflow"
]

オブジェクト - {}

JSONのように、{}はオブジェクト(別名:辞書orハッシュ)を構成するためのものです。

キーは"sensible"(全てアルファベット文字)の場合、引用符はオフにスルことが出来ます。値は、{}式の入力に対して対応する表現(値)に成り得ます。

もしJSONオブジェクト{"bar":42, "baz":43}というのがあったならば、

{foo: .bar}

という表現はJSONオブジェクト{"foo": 42}を生成する事でしょう。

あなたは、オブジェクトの特定のフィールドを選択するために、これを使用することができます。:もし、入力が"user", "title", "id", "content"フィールドを持つオブジェクトで、そこに"user"と"title"だけ情報が欲しい場合は、以下のように書くことが出来ます。

{user: .user, title: .title}

これはとてもポピュラーなもので、{user, title}というショートカット構文があります。

式のいずれかが複数の結果を生成する場合、複数の辞書が生成されます。もし入力が以下内容なら、

{"user":"stedolan","titles":["JQ Primer", "More JQ"]}

表現式はこうなり、

{user, title: .titles[]}

結果はこうなります。

{"user":"stedolan", "title": "JQ Primer"}
{"user":"stedolan", "title": "More JQ"}

鍵括弧を置くことは、それが式として評価されることを意味します。上記と同じ入力で、

{(.user): .titles}

この式は以下の結果を生成します。

{"stedolan": ["JQ Primer", "More JQ"]}
$ echo '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}' | jq '{user, title: .titles[]}'
{
  "title": "JQ Primer",
  "user": "stedolan"
}
{
  "title": "More JQ",
  "user": "stedolan"
}

$ echo '{"user":"stedolan","titles":["JQ Primer", "More JQ"]}' | jq '{(.user): .titles}'
{
  "stedolan": [
    "JQ Primer",
    "More JQ"
  ]
}

組み込み演算子と関数

組み込み演算子

+

数値同士の場合は加算。配列同士の場合は配列を連結。文字列の場合も結合。オブジェクトはマージされます。両方の値からkey-valueのペアを追加して行きます。同じキーの要素があった場合は後勝ちで上書き更新されます。

$ echo '{"a": 7}' | jq '.a + 1'
8
$ echo '{"a": [1,2], "b": [3,4]}' | jq '.a + .b'
[
  1,
  2,
  3,
  4
]
$ echo '{"a": 1}' | jq '.a + null'
1
$ echo {} | jq '.a + 1'
1
$ echo null | jq '{a: 1} + {b: 2} + {c: 3} + {a: 42}'
{
  "c": 3,
  "b": 2,
  "a": 42
}

-

数値の場合は減算。配列要素に対しては、指定した要素を除去します。

$ echo '{"a":3}' | jq '4 - .a'
1
$ echo '["xml", "yaml", "json"]' | jq '. - ["xml", "yaml"]'
[
  "json"
]

/, *

乗算、除算を行います。これらの演算子は数字に対してのみ作用・操作するように期待されています。

$ echo 5 | jq '10 / . * 3'
6

関数

length

値の種類数、件数を取得します。

  • 文字列の場合、文字列長(純粋ASCIIの場合、JSONエンコードされた時の長さと同じ)。
  • 配列の場合、要素数。
  • オブジェクトの場合、key-valueペアの数。
  • 値がnullの場合、長さはnullとなります。
$ echo '[[1,2], "string", {"a":2}, null]' | jq '.[] | length'
2
6
1
0

keys

オブジェクトの場合、キーの一覧を返します。数値配列の場合、0始まりのインデックスを返します。

$ echo '{"abc": 1, "abcd": 2, "Foo": 3}' | jq 'keys'
[
  "Foo",
  "abc",
  "abcd"
]
$ echo '[42,3,35]' | jq 'keys'
[
  0,
  1,
  2
]

has

対象のオブジェクトが指定のkeyを保つ場合、または指定の数値インデックスを持つ場合、trueを返します。

$ echo '[{"foo": 42}, {}]' | jq 'map(has("foo"))'
[
  true,
  false
]
$ echo '[[0,1], ["a","b","c"]]' | jq 'map(has(2))'
[
  false,
  true
]

to_entries, from_entries, with_entries

これらは、key-valueのペアからオブジェクトや配列へ変換する機能です。

  • to_entries:入力値のk:vそれぞれを{"key": k, "value": v}に変換します。
  • from_entries:to_entriesとは逆の変換を行います。
  • with_entries:to_entries | map(foo) | from_entriesの省略形。全てのkeyとvalueに何らかの操作を加えたい場合に便利です。
$ echo '{"a": 1, "b": 2}' | jq 'to_entries'
[
  {
    "value": 1,
    "key": "a"
  },
  {
    "value": 2,
    "key": "b"
  }
]
$ echo '[{"key":"a", "value":1}, {"key":"b", "value":2}]' | jq 'from_entries'
{
  "b": 2,
  "a": 1
}
$ echo '{"a": 1, "b": 2}' | jq 'with_entries(.key |= "KEY_" + .)'
{
  "KEY_b": 2,
  "KEY_a": 1
}

select

select(foo)のfooの条件を満たす場合、それらに該当する結果を返します。それ以外の場合は何も返しません。

これはリストをフィルタリングする場合に便利です。[1,2,3] | map(select(. >= 2))を実行すると、3を返します。

$ echo '[1,5,3,0,7]' | jq 'map(select(. >= 2))'
[
  5,
  3,
  7
]

empty

この関数は何も返しません。nullでさえもです。

$ echo null | jq '1, empty, 2'
1
2
$ echo null | jq '[1,2,empty,3]'
[
  1,
  2,
  3
]

map(x)

入力配列の値全てに対し、処理を実行、新しい配列を生成します。map(.+1)とした場合、数値配列全ての値を+1します。map(x)[.[] | x]と同意です。

$ echo '[1,2,3]' | jq 'map(.+1)'
[
  2,
  3,
  4
]

add

配列要素の内容を連結・合算します。文字列の場合は文字列連結、数値の場合は合算。

$ echo '["a","b","c"]' | jq 'add'
"abc"
$ echo '[1, 2, 3]' | jq 'add'
6
$ echo '[]' | jq 'add'
null

range

指定範囲の数値群を生成します。range(4;10)とした場合、6つの数字(4〜9)を生成します。この場合値をそれぞれ別個の値として出力するので、もし配列として生成したい場合は[range(4;10)]とする必要があります。

$ echo null | jq 'range(2;4)'
2
3
$ echo null | jq '[range(2;4)]'
[
  2,
  3
]

tonumber

要素を数値としてパースします。

$ echo '[1, "1"]' | jq '.[] | tonumber'
1
1

tostring

要素を文字列としてパースします。

$ echo '[1, "1", [1]]' | jq '.[] | tostring'
"1"
"1"
"[1]"

type

要素の型を返します。

$ echo '[0, false, [], {}, null, "hello"]' | jq 'map(type)'
[
  "number",
  "boolean",
  "array",
  "object",
  "null",
  "string"
]

sort, sort_by

入力値をソートします。入力値は配列である必要があります。値は以下の順序でソートされます。

  • null
  • false
  • true
  • 数値
  • 文字列(Unicodeコードポイント値によるアルファベット順)
  • 配列
  • オブジェクト

sort_byはオブジェクトの特定フィールドによるソート、また何らかのjqフィルタを適用させた内容でソートを行いたい場合に用いられるでしょう。sort_by(foo)は全ての要素に対してfoo関数を適用させた結果を比較することによってソートを行います。

$ echo '[8,3,null,6]' | jq 'sort'
[
  null,
  3,
  6,
  8
]
$ echo '[{"foo":4, "bar":10}, {"foo":3, "bar":100}, {"foo":2, "bar":1}]' | jq 'sort_by(.foo)'
[
  {
    "bar": 1,
    "foo": 2
  },
  {
    "bar": 100,
    "foo": 3
  },
  {
    "bar": 10,
    "foo": 4
  }
]

group_by

group_by(.foo)は入力として配列や、.fooフィールドを持つ異なる配列要素のグループを扱い、.fooフィールドでソートされたより大きな配列要素を出力として生成します。

$ echo '[{"foo":1, "bar":10}, {"foo":3, "bar":100}, {"foo":1, "bar":1}]' | jq '.'
[
  {
    "bar": 10,
    "foo": 1
  },
  {
    "bar": 100,
    "foo": 3
  },
  {
    "bar": 1,
    "foo": 1
  }
]
$ echo '[{"foo":1, "bar":10}, {"foo":3, "bar":100}, {"foo":1, "bar":1}]' | jq 'group_by(.foo)'
[
  [
    {
      "bar": 10,
      "foo": 1
    },
    {
      "bar": 1,
      "foo": 1
    }
  ],
  [
    {
      "bar": 100,
      "foo": 3
    }
  ]
]

min, min_by, max, max_by

最小値、最大値を取得します。_byが付いている場合、任意のフィールドを指定する事が出来ます。例えばmin_by(.foo)の場合、最も小さいfooフィールドの値を探します。

$ echo '[5,4,2,7]' | jq 'min'
2
$ echo '[5,4,2,7]' | jq 'max'
7
$ echo '[{"foo":1, "bar":14}, {"foo":2, "bar":3}]' | jq 'max_by(.foo)'
{
  "bar": 3,
  "foo": 2
}

unique

入力値となる配列から、重複を削除且つソート済みの配列を生成します。

$ echo '[1,2,5,3,5,3,1,3]' | jq 'unique'
[
  1,
  2,
  3,
  5
]

reverse

配列要素を逆順にします。

$ echo [1,2,3,4] | jq 'reverse'
[
  4,
  3,
  2,
  1
]

contains

入力値に指定値が完全一致で含まれている場合、trueを返します。

$ echo '["foobar", "foobaz", "blarp"]'
["foobar", "foobaz", "blarp"]
$ echo '["foobar", "foobaz", "blarp"]' | jq 'contains(["baz", "bar"])'
true
$ echo '["foobar", "foobaz", "blarp"]' | jq 'contains(["bazzzzz", "bar"])'
false
$ echo '{"foo": 12, "bar":[1,2,{"barp":12, "blip":13}]}' | jq 'contains({foo: 12, bar: [{barp: 12}]})'
true
$ echo '{"foo": 12, "bar":[1,2,{"barp":12, "blip":13}]}' | jq 'contains({foo: 12, bar: [{barp: 15}]})'
false

recurse

recurseを利用すると、再帰的な構造を検索し、すべてのレベルから対象となるデータを抽出することができます。あなたが入力した内容をファイルシステムとして扱いします。

今、現在の入力値から全てのファイル名を抽出したいとします。その際、

  • .name
  • .children[].name,
  • .children[].children[].name, …

といった感じで探す必要が出てくるでしょう。この関数ではそれらを実施してくれます。

$ echo '{"foo":[{"foo": []}, {"foo":[{"foo":[]}]}]}' | jq 'recurse(.foo[])'
{
  "foo": [
    {
      "foo": []
    },
    {
      "foo": [
        {
          "foo": []
        }
      ]
    }
  ]
}
{
  "foo": []
}
{
  "foo": [
    {
      "foo": []
    }
  ]
}
{
  "foo": []
}

文字列の補間(String interpolation)

文字列内で、バックスラッシュの後に括弧で囲んだ式を置く事が出来ます。表現を返すものは何でも、文字列として補間されるでしょう。

$ echo 42 | jq '"The input was \(.), which is one less than \(.+1)"'
"The input was 42, which is one less than 43"

文字列フォーマットとエスケープ(Format strings and escaping)

@fooシンタックスは使われる文字列フォーマットとエスケープで使われます。これはHTMLやXMLなどの言語でのURLやドキュメントを構築する際に便利です。@fooは独自にフィルタとして使う事が出来ます。利用可能なものは以下。

  • @text: tostringを呼び出します。
  • @json: 入力値をJSONとしてシリアライズします。
  • @html: HTML/XMLエスケープを適用します。
  • @uri: %xxのシーケンスにすべての予約URI文字をマッピングすることで、パーセントエンコーディングを適用します。
  • @csv: 入力値は配列である必要があります。二重文字列の引用符、および繰り返しでエスケープ引用符でCSVとしてレンダリングされます。
  • @sh: 入力はPOSIXシェルのコマンドラインでの使用に適した形にエスケープされます。入力が配列である場合、出力はスペースで区切られた一連の文字列になります。
  • @base64: 入力値はRFC4648に基づき、base64変換されます。

この構文は、便利な形で文字列補間と組み合わせる事が出来ます。

$ echo '"This works if x < y"' | jq '@html'
"This works if x < y"
$ echo '"O'Hara's Ale"' | jq '@sh "echo \(.)"'
"echo 'OHaras Ale'"

条件文と比較

==, !=

aとbが等価の場合、'a == b'でtrueを返し、そうでない場合はfalseを返します。

具体的に言うと、文字列は数値と等しい、とは見なされません。もしあなたがJavascript畑の人であれば、jqの'=='はJavascriptの'==='みたいなもの - 同じ型且つ同じ値を持っている時に等しい - と考えてください。

$ echo '[1, 1.0, "1", "banana"]' | jq '.[] == 1'
true
true
false
false

if-then-else

ちょっとここは訳しづらいw まぁここはif-then-else構文もjqで使えますよ、と言う事で。

もし条件合致要素が復数となる場合、結果のいずれかがfalseまたはnullでなければ、それはtrueと見なされます。また、結果が0件であれば、それはfalseと見なされます。

$ echo 2 | jq 'if . == 0 then
>   "zero"
> elif . == 1 then
>   "one"
> else
>   "many"
> end'
"many"

>, >=, <=, <

大小比較などに用いる不等号も利用可能。順序に関するルールは上記『sort』の項で説明したものと同じです。

$ echo 2 | jq '. < 5'
true

and/or/not

jqは通常のブール演算子、and/or/notもサポートしています。

一般的な表現同様、falseまたはnullの場合は"false values"と見なされ、またそれ以外の場合は"true value"と見なされます。

もしこれらの演算子のオペランドが復数の結果を返した場合、演算子自身は各入力に対する結果を生成します。

実のところ、notは演算子と言うよりもビルトイン関数だったりします。なので、特別なシンタックスと言うよりも、.foo and .bar | not.の言うような形でフィルタとして呼ばれます。

$ echo null | jq '42 and "a string"'
true
$ echo null | jq '(true, false) or false'
true
false
$ echo null | jq '(true, true) and (true, false)'
true
false
true
false
$ echo null | jq '[true, false | not]'
[
  false,
  true
]

代替演算子(Altenative Operator) - //

a // bのフィルタは、もしaがfalseまたはnullでなければaとして同じ結果を返します。そうでない場合はbとして結果を返します。

これは、デフォルト値を提供したい場合便利です。.foo // 1 はもし.fooが入力値に存在しない場合、1を返します。

$ echo '{"foo": 19}' | jq '.foo // 42'
19
$ echo '{}' | jq '.foo // 42'
42

高度な機能

変数は大抵のプログラミング言語に於いて、絶対的に必要なものなのですが、ことjqにおいては"高度な機能"の方へ追いやられてしまっています。殆どの言語では、変数はデータを渡す唯一の手段です。値を計算し、利用したい場合は変数に値を格納する必要があります。

プログラムの別の部分に値を渡すには、データを格納するするためにプログラムに変数を定義する必要があります。

jqでは関数を定義する事も可能ですが、これは標準ライブラリを定義する事が最も利用頻度の高いjqの特長とも言えます。(実際、jqではmapfind等はjqで書かれています。

jqでは非常に強力ですが少しトリッキーな『Reduce』操作を行う事が出来ます。繰り返しになりますが、それらは主にjqの標準ライブラリの 幾つかの有用なビットを定義するために内部的に扱われています。

変数

jqでは、全てのフィルタは入力と出力を持っています。なのでパイプを使って次へ値を渡す必要はありません。多くの表現、例えばa + bは2つの異なる部分式へ同じ入力を渡しますので、値を渡す為の変数は通常必要ありません。

例えば、多くの言語で、数値配列の平均値計算は幾つかの変数を必要とします。 - 少なくとも配列保持に1つ、そしてもう1つ、ループカウンター用に。

jqに於いて、add / length - addは配列を与えられ、そこから合計を算出しています。そしてlengthは配列を与えられ、その長さを算出しています。

なので、変数を定義しているjqで殆どの問題を解決することは一般的にクリーンな手法です。でも、物事をより簡単にする為に、jqでexpression as $variable.を用いる事で変数を定義する事が出来ます。

全ての変数は$で始まります。以下は配列要素の平均値を求める"少し醜い"例です。

length as $array_length | add / $array_length

変数を使用する事で作業がより容易になるような解法を見つけるために、より複雑な問題を探して見ましょう。

私たちは、ブログ投稿に関する"author", "title"フィールドを持つ配列、そして"author"ユーザー名:リアルユーザー名のMapを使っている別のオブジェクトを持っていると仮定しましょう。入力値は以下のようになります。

{"posts": [{"title": "Frist psot", "author": "anon"},
           {"title": "A well-written article", "author": "person1"}],
 "realnames": {"anon": "Anonymous Coward",
               "person1": "Person McPherson"}}

以下のように、実名を含む著者のフィールドを持つデータを作成したいとします。

{"title": "Frist psot", "author": "Anonymous Coward"}
{"title": "A well-written article", "author": "Person McPherson"}

リアルユーザー名をオブジェクトに格納するために、$names変数を使います。そうする事で後で著者名参照出来るようになります。

.realnames as $names | .posts[] | {title, author: $names[.author]}

exp as $x | ... はforループのようなものとして機能します。変数は式の残りの部分に渡ってスコープ対象となるので、以下例の1行目は動きますが、2行目は動きません。

.realnames as $names | (.posts[] | {title, author: $names[.author]}) (.realnames as $names | .posts[]) | {title, author: $names[.author]}

]$ echo '{"foo":10, "bar":200}' | jq '.bar as $x | .foo | . + $x'
210

関数の定義

"def"シンタックスを用いる事で、関数を独自に定義する事が出来ます。

def increment: . + 1;

宣言以降は、incrementは組み込み関数のようなフィルタとして利用する事が出来る様になります。関数は、以下の様に引数を取るかもしれません。

def map(f): [.[] | f];

引数は値としてではなく、フィルタとして渡されます。同様の引数は、別の異なる入力(ここでfは入力配列の各要素に対して実行される)に複数回参照されるかも知れません。関数の引数は、値の引数のように、と言うよりもコールバックのように動きます。

もしシンプルな関数の定義として動作させたい場合は、単に変数を利用する事が出来ます。

def addvalue(f): f as $value | map(. + $value);

ここでは、addvalue(.foo)は配列の各要素に対して現在の入力値の.fooを追加します。

$ echo '[[1,2],[10,20]]' | jq 'def addvalue(f): . + [f]; map(addvalue(.[0]))'
[
  [
    1,
    2,
    1
  ],
  [
    10,
    20,
    10
  ]
]

$ echo '[[1,2],[10,20]]' | jq 'def addvalue(f): f as $x | map(. + $x); addvalue(.[0])'
[
  [
    1,
    2,
    1,
    2
  ],
  [
    10,
    20,
    1,
    2
  ]
]

Reduce

jqではreduceを利用すると、単一の答えに対して蓄積をして行く事で、式の全ての結果を結合する事が出来ます。例として、以下の式に[3,2,1]を入力値として渡します。

reduce .[] as $item (0; . + $item)

.[]を生成する全ての結果に対し、. + $itemは実行中の合計を蓄積する為に0から始まり実行されます。この例では、.[]が結果3,2,そして1を生成します。効果としては以下のような感じで実行した事と同じになります。

0 | (3 as $item | . + $item) | 
    (2 as $item | . + $item) |
    (1 as $item | . + $item)
$ echo '[1,3,5,9,2,8,10,4,6,7]' | jq 'reduce .[] as $item (0; . + $item)'
55

割り当て(Assignment)

(Assignment...割り当て?仕事?任務/任命?しっくり来るフレーズが...。ここではひとまず『割り当て』と略して進めてみます。)

jqに於いて割り当ては、多くのプログラミング言語とはちょっと異なる挙動をします。jqは"参照"と"何かのコピー"を区別しません。もしオブジェクトが配列の.foo.barというフィールドを持っている場合、.fooに何かを追加しても、.barは大きくなる事はありません。

=

フィルター.foo = 1は入力としてオブジェクトを取り、出力として『fooフィールドに1が設定された』オブジェクトを生成します。jqでは、『変更』または『何かを変える』という概念はありません。jqでは全ての値は不変です。

|=

=だけでなく、jqは"更新"演算子|=も提供しています。これは右側にフィルタリングが掛かり、式を通して古い値を実行する事によってプロパティに割り当てられている新しい値を操作します。

以下例では、=と|=の挙動の違いを示しています。

前者は、入力の"b"のフィールドへ入力の"a"のフィールドを設定し、{"a":20}を生成しています。後者は、"a"のフィールドを"a"のフィールドの"b"フィールドに設定し、{"a":10}を生成しています。

$ echo '{"a":{"b":10}, "b":20}' | jq '.a = .b'
{
  "b": 20,
  "a": 20
}

$ echo '{"a":{"b":10}, "b":20}' | jq '.a |= .b'
{
  "b": 20,
  "a": 10
}

+=, -=, *=, /=. //=

jqは、幾つかのa op= b形式の演算子を持っています。形としては、a |= . op bと同じです。なので、+= 1は値を増分します。

$ echo '{"foo": 42}' | jq '.foo + 1' 
43

複雑な割り当て

より多くのものが、jqでの割り当てに於いて"左側"に許可されています。これはその他の言語よりも多いです。

私たちは既に"左側で"シンプルなフィールドアクセスを見てきました。そして、配列アクセスだけでも同様に動作する事も驚くべき事ではありません。

.posts[0].title = "JQ Manual"

驚くべき事かも知れない点は、左側の式は、入力文書内の異なるポイントを参照して、複数の結果を生む可能性があると言う事です。

.posts[].comments |= . + ["this is great"]

例では、文字列"this is great"を配列要素の"comments"に追記しています。(入力値は、投稿データの配列の"posts"を持つオブジェクトです)

jqは、'a = b'のような割り当てを検出すると、aを実行中、入力文書の一部を選択する為に取られる"パス"を記録します。

このパスは、割当の実行中に入力値のどの部分を変更したかを見つけるために使用されます。

我々は上記と同じ"ブログ"入力を使用して、記事をブログにコメントを追加したいと仮定します。今回は、我々は唯一の"stedolan"によって書かれた記事についてコメントしたい。私たちは、前述の"セレクト"機能を使用して、これらの記事を見つけることができます:

.posts[] | select(.author == "stedolan")

この操作によって提供されたパスは、"stedolan"が書いた投稿それぞれを指し示します。そして、私たちはそれらに対し、前に行ったのと同じ方法でコメントする事が出来ます。

(.posts[] | select(.author == "stedolan") | .comments) |= . + ["terrible."]

まとめ

以上、ざっくりと言いながらボリューム的にはかなりがっつりな感じになってしまいました。訳文的にはかなり怪しいところもあると思いますので、改善箇所の御指摘などありましたらコメント等で頂けると非常に助かります。ひと通りマニュアルを読み、実践してみる事でだいぶ理解が進んだような気がします。基本的な関数等を組み合わせて行くだけでも十分便利な使い方が出来る『jq』、皆さんもこの機会に色々触ってお試しになってみてはいかがでしょうか。