如何使用 jq 搜索和替换数组中的多个值?

如何使用 jq 搜索和替换数组中的多个值?

在以下 json 文件中,

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

我需要根据姓名更新两个电话号码,并将整个 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

也就是说,我将名称放入一个数组中,将新数字放入另一个数组中,然后循环索引并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然后您可以在一次调用中更新数字:

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 个值,您可以在一次 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"'

相关内容