Как преобразовать ответ 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 [email protected]"]
    }
  ]
  }

Я фильтрую это с помощью этого:

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.

Любая помощь или подсказка были бы хороши, заранее спасибо ...

Вы можете использовать python / perl со встроенными модулями YAML и JSON - commandlinefu.com/commands/view/12218/convert-yaml-to-json

Inian 15.11.2018 10:11

Это прямо противоположное направление

Jan 15.11.2018 10:18

Сам Bash не подходит для этого. Найдите существующий инструмент или напишите его, например, Python. В зависимости от вашего предпочтительного языка не составит труда найти существующий вопрос о переполнении стека с предложениями; вот поиск Python

tripleee 15.11.2018 11:04

Пожалуйста, следуйте правилам минимальный воспроизводимый пример. В частности, будет полезен образец my.yml (т.е. input).

peak 15.11.2018 11:17
yq r input.json должен сделать эту работу.
AHT 15.11.2018 19:15

Спасибо всем, я последовал подходу @Inian, чтобы написать lil-скрипт на Ruby. и @ Джефф Меркадо. Не знаю, почему это цитируется, но это именно то, на что программа наплевывает.

Jan 15.11.2018 20:23

как отметил @AHT, yq r работает (поскольку JSON является подмножеством YAML); вывод должен быть красиво напечатан, хотя чтобы он «больше походил на YAML»: yq -P r input.json; см. также mikefarah.gitbook.io/yq/usage/convert#json-to-yaml

ssc 17.02.2020 13:16
Спецификация YAML говорит: «каждый файл JSON также является допустимым файлом YAML».
djvg 28.09.2021 14:40
Как сделать HTTP-запрос в Javascript?
Как сделать HTTP-запрос в Javascript?
В JavaScript вы можете сделать HTTP-запрос, используя объект XMLHttpRequest или более новый API fetch. Вот пример для обоих методов:
27
8
33 484
8

Ответы 8

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

Насколько я понимаю, скалярные значения просто выводятся как есть (с потенциальной кодировкой), объекты выводятся как пары ключ / значение, а объекты массивов выводятся с - для каждого элемента. Отступ ассоциирует то, что является частью чего.

Итак, исходя из этих правил, если вы собираетесь использовать 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 [email protected]

Вот еще один вариант, который выравнивает значения

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 [email protected]

очень круто. люблю, к сожалению, он ломается для многострочных строковых значений ..

Kaos 02.06.2020 11:57

На основе кода @Jeff Mercado я добавил код для поддержки многострочных строк и экранирования одинарных кавычек. См. Мой ответ «Решение в jq (без других инструментов)».

jpseng 29.06.2021 22:01

Я использовал 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

Ваш yaml-to-json oneliner не работает с found character '\t' that cannot start any token; это то же самое сообщение об ошибке, которое сообщает yq, которое должно работать аналогичным образом. Однострочник Ричарда Гомеса отлично работает.

Bean Taxi 29.03.2020 17:40
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

Возможно, излишний в свете yq и jq, но сделан очень хорошо и, вероятно, имеет хорошие приложения.

abalter 02.10.2020 07:52

Ха! Так случилось, что я столкнулся с ошибками при установке yq только сейчас в ноутбуке gpc ai. так что, я думаю, они вам очень пригодятся!

abalter 02.10.2020 08:00

возможно, лучший ответ, так как он не заставляет вас устанавливать yq

P.... 05.07.2021 19:53

yq обертка yaml для jq

В yq версии 4.8.0:

cat $DEFAULTS_FILE | yq e -P -

  • e или eval обрабатывают файл отдельно. ea или eval-all сначала объединят файлы.
  • -P или --prettyPrint YAML выход
  • - от STDIN

Примечание: вы также можете пойти другим путем (от yaml к json) yq e -j file.yaml

В yq версии 3.3.2:

cat $DEFAULTS_FILE | yq r -P -

  • r читать
  • -P --prettyPrint
  • - от STDIN

Ты сумасшедший парень! Спасибо друг!

Robert J 29.09.2020 17:07

Этот ответ требует редактирования. Ваш пример yq - это реализация mikefarah / yq, которая НЕ является оболочкой для jq. Смотрите мой отвечать

Inian 01.11.2020 16:25

Если вы используете mikefarah / yq V4, вам необходимо использовать следующую команду: yq eval '.. style= ""' sample.json или cat sample.json | yq eval '.. style= ""' -. См. mikefarah.gitbook.io/yq/usage/convert

ethanabrooks 25.12.2020 15:07

Собственно, все, что вам нужно, это cat $DEFAULTS_FILE | yq -y

Ben Davis 03.01.2021 06:06

Для mikefarah/yq версии 4 yq eval -P является правильным синтаксисом.

ijoseph 28.02.2021 04:58

С mikefarah / yq версии 4.15 и yq e -P, и yq eval -P работают нормально.

AATHITH RAJENDRAN 01.12.2021 06:21

Я предлагаю использовать 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 [email protected]

yq eval -P

с mikefarah/yq версии 4.0 (выпущен в декабре 2020 г.), устанавливается через большинство менеджеров пакетов Unix-подобных ОС: через Homebrew для macOS (brew install yq), Debian с apt (apt install yq), Alpine с apk (apk add yq) и т. д.

См. Работа с JSON.

To read in json, just pass in a json file instead of yaml, it will just work - as json is a subset of yaml. However, you will probably want to use the Style Operator or --prettyPrint/-P flag to make look more like an idiomatic yaml document.

Решение в 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'

{
  "response": {
    "code": 200,
    "message": "greeting\nthat's all folks\n\n\n"
  }
}

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

Контрольная работа

Вы можете проверить правильность функции toYaml, преобразовав 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 sec

time for i in {1..100}; do jq -r 'toYaml' > /dev/null containsMultilineStrings.json; done

3.4 sec

Другие вопросы по теме