Как преобразовать ответ JSON в YAML в Bash
Я читаю данные из файла json с помощью jq. Я хочу добавить результаты в файл yaml, но у меня не получается. Я довольно новичок в программировании оболочки. Моя цель - добавить эти "пользователи" к существующему массиву "пользователи" в файле yaml.
Это мой файл JSON:
#$DEFAULTS_FILE
{"users":
[
{"name":"pi",
"gecos": "Hypriot Pirate",
"sudo":"ALL=(ALL) NOPASSWD:ALL",
"shell": "/bin/bash",
"groups":"users,docker,video",
"plain_text_passwd":"pi",
"lock_passwd":"false",
"ssh_pwauth":"true",
"chpasswd": {"expire": false}
},
{"name":"admin",
"gecos": "Hypriot Pirate",
"sudo":"ALL=(ALL) NOPASSWD:ALL",
"shell": "/bin/bash",
"primary-group": "users",
"groups":"users,docker,adm,dialout,audio,plugdev,netdev,video",
"ssh-import-id":"None",
"plain_text_passwd":"pi",
"lock_passwd":"true",
"ssh_pwauth":"true",
"chpasswd": "{expire: false}",
"ssh-authorized-keys": ["ssh-rsa abcdefg1234567890 YOUR_KEY@YOURHOST.local"]
}
]
}
Я фильтрую это с этим:
cat $DEFAULTS_FILE | jq .users
Я понятия не имею, как преобразовать этот JSON в YAML.
Мой ожидаемый результат должен быть:
users:
- name: pi
gecos: "Hypriot Pirate"
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
groups: users,docker,video
plain_text_passwd: pi
lock_passwd: false
ssh_pwauth: true
chpasswd: { expire: false }
- name: admin
primary-group: users
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users,docker,adm,dialout,audio,plugdev,netdev,video
ssh-import-id: None
Я пытался использовать второй инструмент под названием yq
который похож на jq
и может писать файлы yaml. Но у меня нет положительного прогресса.
РЕДАКТИРОВАТЬ
Я знаю, что могу добавить контент в yaml с этим:
yq w -i "my.yml" "users[+]" "some content"
Но я не знаю, как слить мой JSON в это.
Любая помощь или подсказка были бы хороши, спасибо заранее...
13 ответов
С участием yq version 3.3.2
:
cat $DEFAULTS_FILE | yq r -P -
yq
обертка yaml для jq
r
читать
-P
--prettyPrint
-
из STDIN
function yaml_validate {
python -c 'import sys, yaml, json; yaml.safe_load(sys.stdin.read())'
}
function yaml2json {
python -c 'import sys, yaml, json; print(json.dumps(yaml.safe_load(sys.stdin.read())))'
}
function yaml2json_pretty {
python -c 'import sys, yaml, json; print(json.dumps(yaml.safe_load(sys.stdin.read()), indent=2, sort_keys=False))'
}
function json_validate {
python -c 'import sys, yaml, json; json.loads(sys.stdin.read())'
}
function json2yaml {
python -c 'import sys, yaml, json; print(yaml.dump(json.loads(sys.stdin.read())))'
}
Дополнительные трюки Bash на http://github.com/frgomes/bash-scripts
Я не уверен, какие правила вы используете, чтобы получить ожидаемый результат. Кажется, что вы случайным образом применяете разные правила для преобразования значений.
Насколько я понимаю, скалярные значения просто выводятся как есть (с потенциальным кодированием), объекты выводятся в виде пар ключ / значение, а объекты массива выводятся с -
за каждый предмет. Отступы связывают то, что является частью чего.
На основании этих правил, если вы собираетесь использовать jq:
def yamlify:
(objects | to_entries[] | (.value | type) as $type |
if $type == "array" then
"\(.key):", (.value | yamlify)
elif $type == "object" then
"\(.key):", " \(.value | yamlify)"
else
"\(.key):\t\(.value)"
end
)
// (arrays | select(length > 0)[] | [yamlify] |
" - \(.[0])", " \(.[1:][])"
)
// .
;
Затем, чтобы использовать его, добавьте его в свой .jq
файл и использовать его:
$ jq -r yamlify input.json
users:
- name: pi
gecos: Hypriot Pirate
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
groups: users,docker,video
plain_text_passwd: pi
lock_passwd: false
ssh_pwauth: true
chpasswd:
expire: false
- name: admin
gecos: Hypriot Pirate
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
primary-group: users
groups: users,docker,adm,dialout,audio,plugdev,netdev,video
ssh-import-id: None
plain_text_passwd: pi
lock_passwd: true
ssh_pwauth: true
chpasswd: {expire: false}
ssh-authorized-keys:
- ssh-rsa abcdefg1234567890 YOUR_KEY@YOURHOST.local
Вот еще один вариант, который выравнивает значения
def yamlify2:
(objects | to_entries | (map(.key | length) | max + 2) as $w |
.[] | (.value | type) as $type |
if $type == "array" then
"\(.key):", (.value | yamlify2)
elif $type == "object" then
"\(.key):", " \(.value | yamlify2)"
else
"\(.key):\(" " * (.key | $w - length))\(.value)"
end
)
// (arrays | select(length > 0)[] | [yamlify2] |
" - \(.[0])", " \(.[1:][])"
)
// .
;
$ jq -r yamlify2 input.json
users:
- name: pi
gecos: Hypriot Pirate
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
groups: users,docker,video
plain_text_passwd: pi
lock_passwd: false
ssh_pwauth: true
chpasswd:
expire: false
- name: admin
gecos: Hypriot Pirate
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
primary-group: users
groups: users,docker,adm,dialout,audio,plugdev,netdev,video
ssh-import-id: None
plain_text_passwd: pi
lock_passwd: true
ssh_pwauth: true
chpasswd: {expire: false}
ssh-authorized-keys:
- ssh-rsa abcdefg1234567890 YOUR_KEY@YOURHOST.local
yq eval -P
с участием mikefarah/yq
версия 4.0 (выпущена в декабре 2020 г.), устанавливается через большинство менеджеров пакетов Unix-подобных ОС: через Homebrew для macOS (
brew install yq
), Debian с
apt
(
apt install yq
), Альпийский с
apk
(
apk add yq
), так далее.
См. Работа с JSON.
Чтобы читать в json, просто передайте файл json вместо yaml, он просто будет работать - поскольку json является подмножеством yaml. Однако вы, вероятно, захотите использовать Оператор стиля или
--prettyPrint/-P
flag, чтобы он больше походил на идиоматический документ yaml.
Я предлагаю использовать yq
с участием
-y
вариант
$ pip3 install yq # requires jq
$ cat in.json | yq -y
users:
- name: pi
gecos: Hypriot Pirate
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
groups: users,docker,video
plain_text_passwd: pi
lock_passwd: 'false'
ssh_pwauth: 'true'
chpasswd:
expire: false
- name: admin
gecos: Hypriot Pirate
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
primary-group: users
groups: users,docker,adm,dialout,audio,plugdev,netdev,video
ssh-import-id: None
plain_text_passwd: pi
lock_passwd: 'true'
ssh_pwauth: 'true'
chpasswd: '{expire: false}'
ssh-authorized-keys:
- ssh-rsa abcdefg1234567890 YOUR_KEY@YOURHOST.local
Я использовал Ruby, чтобы записать свой json-контент в yaml.
Что касается вашего примера, это можно сделать так:
cat $DEFAULTS_FILE | jq .users | ruby -ryaml -rjson -e 'puts YAML.dump(JSON.parse(STDIN.read))' > my.yml
Еще один лайнер:
python -c 'import yaml, sys; print(yaml.dump(yaml.load(open(sys.argv[1])), default_flow_style=False))' input.json
(используя тот факт, что действительный json также действителен yaml)
И yaml для json:
python -c 'import yaml, json, sys; print(json.dumps(yaml.load(open(sys.argv[1])), indent=2))' input.yaml
Решение в jq (без других инструментов)
На основе кода @Jeff Mercado в этом посте я добавил поддержку многострочных строк и экранирование одинарных кавычек.
# purpose: converts Json to Yaml
# remarks:
# You can use 'yq -y' to convert json to yaml, but ...
# * this function can be used several times within a single jq program
# * this function may be faster than using yq
# * maybe yq is not available in your environment
#
# input: any Json
# output: json converted to yaml
def toYaml:
def handleMultilineString($level):
reduce ([match("\n+"; "g")] # find groups of '\n'
| sort_by(-.offset))[] as $match
(.; .[0:$match.offset + $match.length] +
"\n\(" " * $level)" + # add one extra '\n' for every group of '\n's. Add indention for each new line
.[$match.offset + $match.length:]);
def toYamlString($level):
if type == "string"
then handleMultilineString($level)
| sub("'"; "''"; "g") # escape single quotes
| "'\(.)'" # wrap in single quotes
else .
end;
def _toYaml($level):
(objects | to_entries[] |
if (.value | type) == "array" then
"\(.key):", (.value | _toYaml($level))
elif (.value | type) == "object" then
"\(.key):", "\(" ")\(.value | _toYaml($level))"
else
"\(.key): \(.value | toYamlString($level))"
end
)
// (arrays | select(length > 0)[] | [_toYaml($level)] |
" - \(.[0])", "\(" ")\(.[1:][])"
)
// .;
_toYaml(1);
Пример использования
File 'containsMultilineStrings.json'
jq -r 'toYaml' < containsMultilineStrings.json
response:
code: 200
message: 'greeting
that''s all folks
'
jq -r 'toYaml' containsMultilineStrings.json | yq
(поездка в оба конца)
{
"response": {
"code": 200,
"message": "greeting\nthat's all folks\n\n\n"
}
}
Контрольная работа
Вы можете проверить правильность функции, преобразовав json в yaml, а затем обратно в json с помощью yq.
FILE='containsMultilineStrings.json'; diff <(cat "$FILE") <(jq -r 'toYaml' $FILE | yq)
Представление
Быстрый тест показывает сокращение времени выполнения функции
toYaml
по сравнению с использованием yq. На своем компьютере я измерил:
time for i in {1..100}; do yq -y > /dev/null < containsMultilineStrings.json; done
8.4 с.
time for i in {1..100}; do jq -r 'toYaml' > /dev/null containsMultilineStrings.json; done
3,4 с
Другой вариант — использовать gojq. Это порт jq с поддержкой чтения и записи yaml. Его можно установить с помощью выпусков GitHub, доморощенного и нулевой установки. Команда для вашего вопроса будет:
cat test.json | gojq --yaml-output > test.yaml
Вы можете использовать потрясающую библиотекуyq
# install yq on macOS
brew install yq
Затем выполните следующую команду
cat file.json | yq -P > file.yml
Вы также можете преобразовать и сохранить ответ JSON в файл YAML, например:
curl https://raw.githubusercontent.com/typicode/demo/master/db.json | yq -P > example.yml
кот sample.json | yq -P
По состоянию на 2023 год — ответ @ianmunoz верен
Исходный файл
cat sample.json
[
{"name": "Abc", "id": "10"},
{"name": "Def", "id": "11"},
{"name": "Xyz", "id": "12"}
]
Как Ямл
cat sample.json | yq -P
- name: Abc
id: "10"
- name: Def
id: "11"
- name: Xyz
id: "12"
Как CSV
cat sample.json | yq e '.[0, 1] | [key+1, .id, .name] | @csv'
1,10,Abc
2,11,Def
Примечание. Вы можете извлечь диапазон записей с помощью[0, 1]
и добавьтеkey
на выходе.
В итоге я установил гем и использовал ruby:
gem install deep_merge
( https://github.com/danielsdeleo/deep_merge)
Вот мой подход:
#!/usr/bin/env ruby
#
require 'json'
require 'yaml'
require 'deep_merge/rails_compat'
json_input_file = ARGF.argv[0]
yaml_output_file = ARGF.argv[1]
scope = ARGF.argv[2]
json = File.read(json_input_file)
yaml = File.read(yaml_output_file)
json_data = JSON.parse(json)
scoped_result = json_data[scope]
old_values_hash = YAML.load(File.read(yaml_output_file))
result = YAML.parse(yaml)
merged = old_values_hash.deeper_merge scoped_result
File.write(yaml_output_file, merged.to_yaml)
Затем он может быть использован для чтения конкретного scoped_value
из файла JSON.
jsonyaml.sh $JSON_FILE $YAML_TARGET "my_scope_to_add"
Мне удалось получить результат с помощью следующей команды:
jq -r .users defaultsfile.txt >> users.yaml
Вывод в ясном формате yaml.