Динамическое обновление значений объекта JSON и сохранение их в новом файле в BASH - PullRequest
0 голосов
/ 09 мая 2018

Я совершенно новый (стажер с практическим опытом менее двух недель) в BASH, JSON и jq. Но мне была дана задача итеративно заменить значение объекта JSON из массива. Мне удалось написать следующий код.

Я пытаюсь постепенно заменять значения ключа «имя» в файле json на имена в массиве, хранящемся в другом текстовом файле. По сути, просто обновляем имена узлов.

Вот фрагмент файла JSON, который нужно заменить. Весь файл слишком велик для размещения здесь. Но путь к ключу «имя» - это «.lab.racks []. Node []. Name»

    "roles": [
           "bla bla",
           "bla bla"
      ],
      "name": "Node1",
       "power": {
       "address": "10.182.149.145",
       "type": "bla bla",
       "user": "bla bla",
       "pass": "bla bla "
       },

«name»: «Node1» необходимо заменить на «name»: «Tom-cat». Имя Tom-cat генерируется динамически и изменяется каждый раз, когда запускается скрипт Metal as a Service (MaaS). Это имя "Tom-cat" и все другие новые имена, сгенерированные MaaS, вырезаны (с использованием awk) и сохранены в текстовом файле newhostnames.txt

Текстовый файл выглядит так

#newhostnames.txt
Tom-cat
Lucky-worm
Wom-bat

Таким образом, цель состоит в том, чтобы заменить ключ "name" из demolabconfig.json именами, хранящимися в текстовом файле.

"name": "Node1" must be replaced as "name":"Tom-cat"
];
.....
.....
],
"name": "Node2" must be replaced as "name":"Lucky-worm"
];
.....
.....
],
"name": "Node3" must be replaced as "name":"Wom-bat"

Индексация выполняется для ключевых «узлов»: .lab.racks []. Node [$ i] .name

код:

readarray -t array < newhostnames.txt
array=("${array[@]:1}")
array_length=${#array[@]}
for((i=0;i<${array_length};i++));
do
    declare -x  NEW_NODENAME
    OLD_NODENAME=$(jq -r ".lab.racks[].nodes[$i].name" demolabconfig.json)
    echo "$OLD_NODENAME"
    NEW_NODENAME="${array[i]}"
    echo "$NEW_NODENAME"
    jq ".lab.racks[].nodes[$i].name=env.NEW_NODENAME" demolabconfig.json > newdemolabconfig.json
done

Но код заменяет только одно значение в конце для последней $ i, т.е. последней пары key_value. Все остальные предыдущие узлы сохраняют то же имя, что и в исходном файле JSON.

Я попытался с помощью оператора if разорвать цикл, если значение уже обновлено, поэтому оно не перезаписано из старого файла demolabconfig.json. Но это тоже не работает!

if [[ "$OLD_NODENAME" -ne "$NEWNODENAME" ]]; then
    jq ".lab.racks[].nodes[$i].name=env.NEW_NODENAME" demolabconfig.json > newdemolabconfig.json
else
    break
fi

Это если цикл был написан с циклом while вместо for. Это заменяет фамилию на ноль.

Подскажите, пожалуйста, как я могу исправить эту ошибку и улучшить свой код. Спасибо:)

Ответы [ 2 ]

0 голосов
/ 09 мая 2018

Цель здесь должна состоять в том, чтобы вызывать jq как можно меньше раз. Соответственно, мы представляем решение, которое включает в себя только два вызова в JQ, независимо от количества «имен» в newhostnames.txt.

Сначала давайте предположим, что input.json содержит следующее:

{"nodes":[{"name":"old1"},{"name":"old2"},{"name":"old3"}]}

Давайте также предположим, что следующая программа jq находится в файле program.jq:

  reduce range(0; $newhostnames | length) as $i (.;
    .nodes[$i].name = $newhostnames[$i])

Тогда вызов:

jq -c --slurpfile newhostnames <(jq -nR inputs newhostnames.txt ) \
   -f program.jq input.json

производит:

{"nodes":[{"name":"Tom-cat"},{"name":"Lucky-worm"},{"name":"Wom-bat"}]}

(Параметр командной строки -c создает сжатый вывод.)

Если ваша оболочка не поддерживает вышеуказанный вызов, вы можете (например) поместить вывод jq -nR inputs newhostnames.txt во временный файл.

Конечно, вспомогательная задача преобразования файла .txt в массив JSON или поток строк JSON может быть выполнена другими способами.

Robustification

Если есть вероятность, что newhostnames.txt содержит нежелательные пустые строки, то один из способов пропустить их - добавить одну строку в program.jq, чтобы она выглядела так:

($newhostnames | map(select(length>0))) as $newhostnames
| reduce range(0; $newhostnames | length) as $i (.;
      .nodes[$i].name = $newhostnames[$i])

Обратите внимание, что $ -вариантное имя можно использовать повторно.

0 голосов
/ 09 мая 2018

Самая большая проблема заключается в том, что вы перезаписываете свой измененный файл, используя копию исходного файла на каждом шаге. Вы хотите, чтобы файл new был входным для следующей итерации цикла:

{
   read   # discard the first line
   readarray -t array
} < newhostnames.txt

cp demolabconfig.json newdemolabconfig.json
input=newdemolabconfig.json

for((i=0;i<${#array[@]};i++));
do
    old_nodename=$(jq -r ".lab.racks[].nodes[$i].name" "$input")
    new_nodename="${array[i]}"
    if [[ $old_nodename == $new_nodename ]]; then
        continue
    fi
    jq ".lab.racks[].nodes[$i].name=env.NEW_NODENAME" "$input" > tmp.json
    mv tmp.json "$input"
done
...