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"'

관련 정보