このページの内容

代入

jqの代入は、ほとんどのプログラミング言語と少し異なります。jqは、参照とコピーを区別しません。2つのオブジェクトや配列は、等しいか等しくないかのどちらかであり、「同じオブジェクト」か「別のオブジェクト」かという追加の概念はありません。

オブジェクトに、配列である2つのフィールド .foo と .bar があり、.foo に何かを追加しても、.bar は大きくなりません。以前に .bar = .foo と設定していても同じです。Python、Java、Ruby、JavaScriptなどの言語に慣れている場合は、jqが代入前にすべてのオブジェクトを完全にディープコピーするかのように考えられます。性能のため、実際にはそうしていませんが、大まかな考え方はそのようになります。

このため、jqで循環する値を構築することはできません。たとえば、最初の要素が自分自身である配列は作れません。これは意図的なもので、jqプログラムが生成できるものをすべてJSONで表現できるようにしています。

jqのすべての代入演算子では、左辺(LHS)がパス式です。右辺(RHS)は、左辺のパス式が指定するパスへ設定する値を提供します。

jqの値は、常に不変です。内部では、集約処理を使って、. を置き換える新しい値を計算することで代入を行います。その値には、必要な代入をすべて . に適用してから、変更した値を出力します。次の例で分かりやすくなるかもしれません。{a:{b:{c:1}}} | (.a.b|=3), . は、{"a":{"b":3}} と {"a":{"b":{"c":1}}} を出力します。最後の部分式 . が見るのは、変更した値ではなく、元の値だからです。

多くの場合、|= や += のような変更を行う代入演算子を使い、= は使わない方がよいでしょう。

代入演算子の左辺は、. 内の値を参照することに注意してください。そのため、$var.foo = 1 は期待どおりには動きません。$var.foo は、. 内の有効または有用なパス式ではありません。代わりに $var | .foo = 1 を使います。

また、.a,.b=0 は .a と .b の両方を設定するわけではありませんが、(.a,.b)=0 は両方を設定します。

更新代入:|=

これは「更新」演算子 |= です。右辺にフィルターを取り、代入対象の . のプロパティの古い値を、この式に渡して処理することで、新しい値を計算します。たとえば、(.foo, .bar) |= .+1 は、foo フィールドを入力の foo に1を加えた値に、bar フィールドを入力の bar に1を加えた値に設定したオブジェクトを構築します。

左辺には、一般的なパス式を使えます。path() を参照してください。

|= の左辺が参照するのは、. 内の値であることに注意してください。そのため、$var.foo |= . + 1 は期待どおりには動きません。$var.foo は、. 内の有効または有用なパス式ではありません。代わりに、$var | .foo |= . + 1 を使います。

右辺が何も出力しない場合(つまり、empty の場合)、del(path) と同様に、左辺のパスを削除します。

右辺が複数の値を出力する場合、最初の値だけを使います。互換性に関する注意:jq 1.5以前のリリースでは、最後の値だけを使っていました。

実行例 1

コマンド

jq '(..|select(type=="boolean")) |= if . then 1 else 0 end'

入力

[true,false,[5,true,[true,[false]],false]]

出力 1

[1,0,[5,1,[1,[0]],0]]

算術更新代入:+=, -=, *=, /=, %=, //=

jqには、a op= b という形式の演算子がいくつかあり、すべて a |= . op b と同じです。そのため、+= 1 は、|= . + 1 と同じ意味で、値を1増やすために使えます。

実行例 1

コマンド

jq '.foo += 1'

入力

{"foo": 42}

出力 1

{"foo": 43}

通常の代入:=

これは通常の代入演算子です。他の代入演算子と違い、右辺(RHS)への入力は、左辺(LHS)のパスにある値ではなく、左辺への入力と同じです。また、右辺が出力するすべての値を使います。以下の例を参照してください。

= の右辺が複数の値を生成する場合、jqはそれぞれの値に対して、左辺のパスをその値に設定し、変更した . を出力します。たとえば、(.a,.b) = range(2) は、{"a":0,"b":0} を出力し、その後に {"a":1,"b":1} を出力します。前述の「更新」形式の代入では、このようにはなりません。

次の例で、= と |= の違いが分かるでしょう。

入力 {"a": {"b": 10}, "b": 20} を、次のプログラムに与えます。

.a = .b

および、

.a |= .b

前者は、入力の a フィールドを入力の b フィールドに設定し、{"a": 20, "b": 20} を出力します。後者は、入力の a フィールドを、a フィールド内の b フィールドに設定し、{"a": 10, "b": 20} を出力します。

実行例 1

コマンド

jq '.a = .b'

入力

{"a": {"b": 10}, "b": 20}

出力 1

{"a":20,"b":20}

実行例 2

コマンド

jq '.a |= .b'

入力

{"a": {"b": 10}, "b": 20}

出力 1

{"a":10,"b":20}

実行例 3

コマンド

jq '(.a, .b) = range(3)'

入力

null

出力 1

{"a":0,"b":0}

出力 2

{"a":1,"b":1}

出力 3

{"a":2,"b":2}

実行例 4

コマンド

jq '(.a, .b) |= range(3)'

入力

null

出力 1

{"a":0,"b":0}

複雑な代入

jqの代入の左辺には、ほとんどの言語よりも、多くのものを指定できます。左辺の単純なフィールドへのアクセスはすでに見ました。配列へのアクセスも、同じように使えます。

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

意外かもしれませんが、左側の式は、入力文書の異なる位置を参照する複数の結果を生成できます。

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

この例は、入力内の各記事の"comments"配列に、文字列"this is great"を追加します。入力は、記事の配列である"posts"フィールドを持つオブジェクトです。

jqが'a = b'のような代入に出会うと、aを実行するときに、入力文書の一部を選択するためにたどった「パス」を記録します。代入を実行するときには、このパスを使って、入力のどの部分を変更するかを見つけます。等号の左辺には、どのフィルターでも使えます。そのフィルターが入力から選択するパスが、代入を行う場所になります。

これは非常に強力な操作です。前述の"blog"入力を使って、ブログ記事にコメントを追加したいとします。今回は、"stedolan"が書いた記事だけにコメントしたいとします。前述の"select"関数で、そのような記事を見つけられます。

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

この操作が提供するパスは、"stedolan"が書いた各記事を指しています。以前と同じ方法で、それぞれにコメントできます。

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