jqを使って配列内の複数の値を検索して置き換える方法は?

jqを使って配列内の複数の値を検索して置き換える方法は?

次のjsonファイルから

{
    "contacts": [
        {
            "name": "John",
            "phone": "1234"
        },
        {
            "name": "Jane",
            "phone": "5678"
        }
    ]
}

名前に基づいて2つの電話番号を更新し、json全体を新しいファイルに保存する必要があります。

私は次のことを試しました:

jq '.contacts[] | select(.name == "John") | .phone = "4321"' < contacts.json >updated_contacts.json

しかし、親ノードに戻ってJaneのノードを変更する方法、またはjson全体を検索する方法がわかりません。

私は変数に保存されたルートノードを試しましたが、as同じままです。

一時的な解決策として、次のようにしました。

jq '.contacts[0].number = "4321" | .contacts[1].number = "4321"' < contacts.json >updated_contacts.json

ただし、元のjsonが変更される可能性があるため、配列インデックスに依存してはいけません。名前に依存する必要があります。

jqコマンドを使ってこれを行う方法を知っていますか?

答え1

項目を変更するには、|=更新演算子の左側を使用する必要があります。オリジナル文書:

jq --arg name John --arg phone 4321 \
    '( .contacts[] | select(.name == $name) ).phone |= $phone' file

実際には配列から要素セットを抽出する.contacts[] | select(.name == "John") | .phone |= ...ため、これは使用できません。したがって、文書の主要部分ではなく、抽出された要素のみを変更できます。select()contacts

違いを確認してください

( ... | select(...) ).phone |= ...
^^^^^^^^^^^^^^^^^^^^^
path in original document

これは有効です。

... | select(...) | .phone |= ...
      ^^^^^^^^^^^
      extracted bits

これはうまくいきません。


たとえば、次のように複数の項目にループを使用しますbash

names=( John Jane )
phones=( 4321 4321 )

tmpfile=$(mktemp)

for i in "${!names[@]}"; do
    name=${names[i]}
    phone=${phones[i]}

    jq --arg name "$name" --arg phone "$phone" \
        '( .contacts[] | select(.name == $name) ).phone |= $phone' file >"$tmpfile"
    mv -- "$tmpfile" file
done

つまり、名前を1つの配列に入れ、新しい番号を別の配列に入れてから、インデックスを繰り返し、一時fileファイルと中間ストアを使用して変更する必要がある各項目を更新します。

または連想配列を使用します。

declare -A lookup

lookup=( [John]=4321 [Jane]=4321 )

for name in "${!lookup[@]}"; do
    phone=${lookup[$name]}

    # jq as above
done

次の新しい電話番号を含むJSON入力文書があるとします。

{
   "John": 1234,
   "Jane": 5678
}

これを使って作成できます

jo John=1234 Jane=5678

jqその後、1回の通話で番号を更新できます。

jo John=1234 Jane=5678 |
jq --slurpfile new /dev/stdin \
    '.contacts |= map(.phone = ($new[][.name] // .phone))' file

これは入力JSONを読み取り、次$newの構造に新しい数字を入れます。

[
  {
    "John": 1234,
    "Jane": 5678
  }
]

これは、通話中にmap()リストされた連絡先の電話番号を変更するために使用されます。// .phone名前が記載されていなくても電話番号が変わらないことを確認してください。

答え2

Kusalanandaの回答によると、2つの値のみを検索して置き換えるには、1回のjq呼び出しで次のことを行うことができます。

jq '( .contacts[] | select(.name == "John") ).phone |= "4321" | 
    ( .contacts[] | select(.name == "Jane") ).phone |= "8765"' \
    contacts.json

または、チェーン2 jqは次のように呼び出します。

cat contacts.json | \
jq '( .contacts[] | select(.name == "John") ).phone |= "4321"' | \
jq '( .contacts[] | select(.name == "Jane") ).phone |= "8765"'

関連情報