Как я могу разобрать файл YAML из сценария оболочки Linux?


193

Я хотел бы предоставить структурированный файл конфигурации, который как можно проще для нетехнического пользователя редактировать (к сожалению, это должен быть файл), и поэтому я хотел использовать YAML. Я не могу найти какой-либо способ разбора этого из сценария оболочки Unix, однако.


не прямо ваш вопрос, но вы можете захотеть взглянуть на ansible, если ваш анализ оболочек, в частности, связан с удаленным управлением различными узлами (и инвентаризацией yaml)
eckes

9
Попробуйте использовать yqдля чтения / записи yaml файлов в оболочке. Страница проекта здесь: mikefarah.github.io/yq Вы можете установить инструмент с brew, aptили загрузить бинарный файл. Чтение значения так же просто, какyq r some.yaml key.value
vdimitrov

@kenorb JSON = ут / YAML!
SWE

Я нашел тесно связанные функции GitHub pkuczynski, из которых лучше всего (для меня), что из Jasperes, поддерживается в его собственном
Github

Ответы:


56

Мой вариант использования может совпадать или не совпадать с тем, о чем просил этот оригинальный пост, но он определенно похож.

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

YAML выглядит так:

KEY:                value
ANOTHER_KEY:        another_value
OH_MY_SO_MANY_KEYS: yet_another_value
LAST_KEY:           last_value

Вывод как у дис:

KEY="value"
ANOTHER_KEY="another_value"
OH_MY_SO_MANY_KEYS="yet_another_value"
LAST_KEY="last_value"

Я добился результата с помощью этой строки:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/gнаходит :и заменяет его =", игнорируя ://(для URL)
  • s/$/"/gдобавляет "в конец каждой строки
  • s/ *=/=/g удаляет все пробелы перед =

13
Не уверен, к чему вы клоните, но если вы имеете в виду, что это не работает для всех YAML, вы правы. Вот почему я начал с нескольких квалификаций. Я просто поделился тем, что сработало для моего варианта использования, так как он отвечал на вопрос лучше, чем когда-либо. Это определенно можно расширить.
Кертис Блэквелл

3
немного открыт для внедрения кода, но, как вы сказали, это шаг вперед
Oriettaxx

1
Я только когда-либо писал сценарии оболочки для локального использования, так что меня это не беспокоило. Однако, если вы знаете, как его обезопасить и / или хотели бы уточнить, я определенно буду благодарен.
Кертис Блэквелл

2
Одноуровневый yaml имеет много форм - значения можно разбить на следующую строку с отступом; значения могут быть заключены в кавычки несколькими способами, которые оболочка не будет анализировать; все может быть написано на одной линии с фигурными скобками: {KEY: 'value', ...}; и, возможно, другие. Самое главное, если вы намереваетесь оценить результат в виде шелл-кода, это будет очень небезопасно.
Бени Чернявский-Паскин

281

Вот синтаксический анализатор только для bash, который использует sed и awk для анализа простых файлов yaml:

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p"  $1 |
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]}}
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
      }
   }'
}

Он понимает файлы, такие как:

## global definitions
global:
  debug: yes
  verbose: no
  debugging:
    detailed: no
    header: "debugging started"

## output
output:
   file: "yes"

Который, когда анализируется с использованием:

parse_yaml sample.yml

будет выводить:

global_debug="yes"
global_verbose="no"
global_debugging_detailed="no"
global_debugging_header="debugging started"
output_file="yes"

он также понимает файлы yaml, сгенерированные ruby, которые могут включать символы ruby, например:

---
:global:
  :debug: 'yes'
  :verbose: 'no'
  :debugging:
    :detailed: 'no'
    :header: debugging started
  :output: 'yes'

и выдаст то же, что и в предыдущем примере.

Типичное использование в скрипте:

eval $(parse_yaml sample.yml)

parse_yaml принимает аргумент префикса, чтобы у всех импортированных настроек был общий префикс (что уменьшит риск конфликтов пространства имен).

parse_yaml sample.yml "CONF_"

выходы:

CONF_global_debug="yes"
CONF_global_verbose="no"
CONF_global_debugging_detailed="no"
CONF_global_debugging_header="debugging started"
CONF_output_file="yes"

Обратите внимание, что на предыдущие настройки в файле могут ссылаться более поздние настройки:

## global definitions
global:
  debug: yes
  verbose: no
  debugging:
    detailed: no
    header: "debugging started"

## output
output:
   debug: $global_debug

Еще одно полезное использование - сначала проанализировать файл по умолчанию, а затем пользовательские настройки, которые работают, так как последние переопределяют первые:

eval $(parse_yaml defaults.yml)
eval $(parse_yaml project.yml)

3
Крутой Стефан! Было бы удивительно, если бы он мог превратить -нотацию yaml в собственные массивы bash!
quickshiftin

3
Это должно быть довольно легко сделать, если вы измените строку printf в скрипте awk. Обратите внимание, что bash не имеет поддержки многомерных ассоциативных массивов, поэтому вы получите массив + один ключ на значение. Хм, наверное, стоит перенести это на github ...
Стефан Фарестам

5
Это ожидает стандартного отступа yml от 2 пробелов. Если вы используете 4 пробела, переменные получат два подчеркивания в качестве разделителя, например, global__debugвместо global_debug.
k0pernikus

3
Привет, Вааб. Хотя я уверен, что вы правы, многие читатели хотели бы анализировать реальные файлы YAML из оболочки, но не совсем ясно (по крайней мере, мне), каков будет результат. С помощью этого сценария я решил проблему и определил подмножество, которое имеет разумное отображение на стандартные переменные. Конечно, нет никаких претензий на решение более широкой проблемы анализа реальных файлов YAML.
Стефан Фарестам

3
Он только печатает вывод на экране. Как бы вы получили доступ к значениям позже?
Sattu

97

Я написал shyamlна python для запросов YAML из командной строки оболочки.

Обзор:

$ pip install shyaml      ## installation

Файл YAML примера (со сложными функциями):

$ cat <<EOF > test.yaml
name: "MyName !!"
subvalue:
    how-much: 1.1
    things:
        - first
        - second
        - third
    other-things: [a, b, c]
    maintainer: "Valentin Lab"
    description: |
        Multiline description:
        Line 1
        Line 2
EOF

Основной запрос:

$ cat test.yaml | shyaml get-value subvalue.maintainer
Valentin Lab

Более сложный циклический запрос для сложных значений:

$ cat test.yaml | shyaml values-0 | \
  while read -r -d $'\0' value; do
      echo "RECEIVED: '$value'"
  done
RECEIVED: '1.1'
RECEIVED: '- first
- second
- third'
RECEIVED: '2'
RECEIVED: 'Valentin Lab'
RECEIVED: 'Multiline description:
Line 1
Line 2'

Несколько ключевых моментов:

  • все типы YAML и странности синтаксиса правильно обрабатываются как многострочные, строки в кавычках, встроенные последовательности ...
  • \0 дополняемый вывод доступен для сплошной многострочной манипуляции.
  • простая пунктирная нотация для выбора дополнительных значений (т. е. subvalue.maintainerявляется действительным ключом).
  • доступ по индексу предоставляется последовательностям (то есть: subvalue.things.-1является последним элементом subvalue.thingsпоследовательности.)
  • доступ ко всем элементам последовательности / структур за один раз для использования в циклах bash.
  • Вы можете вывести всю часть файла YAML как ... YAML, который хорошо сочетается для дальнейших манипуляций с shyaml.

Больше примеров и документации доступно на странице shyaml github или на странице PyyPI shyaml .


1
Это круто! Было бы здорово, если бы был флаг, чтобы игнорировать значения yaml, которые являются пустыми в выводе. Прямо сейчас он выводит «ноль». Я использую его вместе с envdir для вывода файла docker-compose в envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket

@JiminyCricket Пожалуйста, используйте страницу выпуска GitHub! Я был бы рад, по крайней мере, следить за этим. ;)
Вааб

1
К сожалению, shyamlэто смехотворно медленно
nowox

44

yq - это легкий и портативный YAML-процессор командной строки

Целью проекта является создание файлов jq или sed из yaml.

( https://github.com/mikefarah/yq#readme )

В качестве примера (украдено прямо из документации ) приведен файл sample.yaml:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples

затем

yq r sample.yaml bob.*.cats

будет выводить

- bananas
- apples

ему просто не хватает фильтрующих возможностей
Антонин

В течение прошлого года на формуле Forme.brew.sh/formula/yq установлено 26 679 экземпляров.
дусиневан

1
@Antonin Я не уверен, что это то, что вы имеете в виду, но похоже, что теперь у него есть некоторые возможности фильтрации: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

Можно передать небольшой скрипт некоторым интерпретаторам, например Python. Простой способ сделать это с помощью Ruby и его библиотеки YAML заключается в следующем:

$ RUBY_SCRIPT="data = YAML::load(STDIN.read); puts data['a']; puts data['b']"
$ echo -e '---\na: 1234\nb: 4321' | ruby -ryaml -e "$RUBY_SCRIPT"
1234
4321

где dataхеш (или массив) со значениями из yaml.

В качестве бонуса, это будет хорошо разбирать переднюю тему Джекилла .

ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md

1
это можно использовать? вы положили yaml эхо на переводчик ruby. но как использовать эту переменную под остальной частью сценария bash?
Znik

Да, это можно использовать. RUBY_SCRIPTПеременная рубиновый скрипт , который может быть записан в файл вместо (бежать с ruby -ryaml <rubyscript_filename>). Он содержит логику для преобразования входного текста в некоторый выходной текст, внутренне сохраняя содержимое в dataпеременную. Echo выводит текст yaml, но cat <yaml_filename>вместо этого вы можете использовать для передачи содержимого файла.
Рафаэль

Извините, но я не вижу этого в примере выше. Сначала переменная RUBY_SCRIPT хранит код для интерпретатора ruby. Следующее эхо-е имитирует любые данные yaml, они перенаправляются в интерпретатор ruby. Это вызывает код ruby ​​как встроенный скрипт и, наконец, выводит на печать примеры переменных «a» и «b». Тогда где переменная загрузка в bash для его остального исполняемого кода? Я вижу только один обходной путь. положить ruby ​​outout во временный_файл, который должен содержать строки: variable = 'value', и после этого загрузить его в bash с помощью '. temporary_file. но это обходной путь, а не разрешение.
Znik

1
@Znik, как только у вас есть что-то на stdout, созданное чем-то, что подается с помощью stdin, все остальное зависит от рук bash-кодера (и, как напоминание, если вам нужно stdoutпередать данные в переменную, вам не нужно полагаться на временные файлы! использовать x=$(...)или даже read a b c < <(...)). Таким образом, это правильное решение, когда вы точно знаете, что хотите получить в файле YAML, и знаете, как написать строки рубина для доступа к этим данным. Даже если это грубо, это полное доказательство идеи идеи ИМХО. Тем не менее, это правда, что он не предоставляет вам полную абстракцию bash.
Вааб

Да, это так. Ты прав. Спасибо за этот трюк. Использовать одну переменную просто. но многие переменные не являются. трюк со списком переменных чтения <<(выполнение в stdout) очень
полезен

23

Учитывая, что Python3 и PyYAML в настоящее время представляют собой довольно простые зависимости, может помочь следующее:

yaml() {
    python3 -c "import yaml;print(yaml.safe_load(open('$1'))$2)"
}

VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")

Я люблю shyaml, но на отключенных системах это спасение жизни. Должно работать с подавляющим большинством python2, например, RHEL.
rsaw

2
Может быть, использовать yaml.safe_loadкак это безопаснее. pyyaml.org/wiki/PyYAMLDocumentation
Джордан Стюарт,

14

Вот расширенная версия ответа Стефана Фарестама:

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|,$s\]$s\$|]|" \
        -e ":1;s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: [\3]\n\1  - \4|;t1" \
        -e "s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s\]|\1\2:\n\1  - \3|;p" $1 | \
   sed -ne "s|,$s}$s\$|}|" \
        -e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1  \3: \4|;t1" \
        -e    "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1  \2|;p" | \
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p" \
        -e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|p" \
        -e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" | \
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}}
      if(length($2)== 0){  vname[indent]= ++idx[indent] };
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) { vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, vname[indent], $3);
      }
   }'
}

Эта версия поддерживает -обозначения и краткие обозначения для словарей и списков. Следующий вход:

global:
  input:
    - "main.c"
    - "main.h"
  flags: [ "-O3", "-fpic" ]
  sample_input:
    -  { property1: value, property2: "value2" }
    -  { property1: "value3", property2: 'value 4' }

производит этот вывод:

global_input_1="main.c"
global_input_2="main.h"
global_flags_1="-O3"
global_flags_2="-fpic"
global_sample_input_1_property1="value"
global_sample_input_1_property2="value2"
global_sample_input_2_property1="value3"
global_sample_input_2_property2="value 4"

как вы можете видеть, -элементы автоматически нумеруются, чтобы получить разные имена переменных для каждого элемента. В bashнем нет многомерных массивов, так что это один из способов обхода. Несколько уровней поддерживаются. Чтобы обойти проблему с конечными пробелами, упомянутую @briceburg, нужно заключить значения в одинарные или двойные кавычки. Однако есть некоторые ограничения: расширение словарей и списков может давать неправильные результаты, если значения содержат запятые. Кроме того, более сложные структуры, такие как значения, охватывающие несколько строк (например, ssh-ключи), (пока) не поддерживаются.

Несколько слов о коде: первая sedкоманда расширяет краткие формы словарей { key: value, ...}до обычных и преобразует их в более простой стиль yaml. Второй sedвызов делает то же самое для коротких обозначений списков и преобразует [ entry, ... ]их в подробный список с -обозначениями. Третий sedвызов является исходным, который обрабатывает обычные словари, теперь с добавлением для обработки списков с -отступами. awkЧасть вводит индекс для каждого уровня отступа и увеличивает его , когда имя переменной пусто (т.е. при обработке списка). Текущее значение счетчиков используется вместо пустого vname. При подъеме на один уровень счетчики обнуляются.

Изменить: я создал хранилище GitHub для этого.


11

Трудно сказать, потому что это зависит от того, что вы хотите, чтобы парсер извлек из вашего документа YAML. Для простых случаев, вы можете быть в состоянии использовать grep, cut, и awkт.д. Для более сложного разбора вам нужно будет использовать полномасштабный разборе библиотеку , такие как Пайтон PyYAML или YAML :: Perl .


11

Я просто написал парсер, который я назвал Yay! ( Yaml не Yamlesque! ), Который анализирует Yamlesque , небольшое подмножество YAML. Итак, если вы ищете 100% совместимый YAML-парсер для Bash, то это не так. Однако, если процитировать OP, если вы хотите, чтобы структурированный файл конфигурации был как можно более легким для нетехнического пользователя для редактирования , подобного YAML, это может представлять интерес.

Он вдохновлен более ранним ответом, но записывает ассоциативные массивы ( да, для этого требуется Bash 4.x ) вместо базовых переменных. Это делается таким образом, что позволяет анализировать данные без предварительного знания ключей, чтобы можно было писать код, управляемый данными.

Помимо элементов массива ключ / значение, каждый массив имеет keysмассив, содержащий список имен ключей, childrenмассив, содержащий имена дочерних массивов, и parentключ, который ссылается на его родителя.

Это является примером Yamlesque:

root_key1: this is value one
root_key2: "this is value two"

drink:
  state: liquid
  coffee:
    best_served: hot
    colour: brown
  orange_juice:
    best_served: cold
    colour: orange

food:
  state: solid
  apple_pie:
    best_served: warm

root_key_3: this is value three

Вот пример, показывающий, как его использовать:

#!/bin/bash
# An example showing how to use Yay

. /usr/lib/yay

# helper to get array value at key
value() { eval echo \${$1[$2]}; }

# print a data collection
print_collection() {
  for k in $(value $1 keys)
  do
    echo "$2$k = $(value $1 $k)"
  done

  for c in $(value $1 children)
  do
    echo -e "$2$c\n$2{"
    print_collection $c "  $2"
    echo "$2}"
  done
}

yay example
print_collection example

какие выводы:

root_key1 = this is value one
root_key2 = this is value two
root_key_3 = this is value three
example_drink
{
  state = liquid
  example_coffee
  {
    best_served = hot
    colour = brown
  }
  example_orange_juice
  {
    best_served = cold
    colour = orange
  }
}
example_food
{
  state = solid
  example_apple_pie
  {
    best_served = warm
  }
}

А вот и парсер:

yay_parse() {

   # find input file
   for f in "$1" "$1.yay" "$1.yml"
   do
     [[ -f "$f" ]] && input="$f" && break
   done
   [[ -z "$input" ]] && exit 1

   # use given dataset prefix or imply from file name
   [[ -n "$2" ]] && local prefix="$2" || {
     local prefix=$(basename "$input"); prefix=${prefix%.*}
   }

   echo "declare -g -A $prefix;"

   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
          -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |
   awk -F$fs '{
      indent       = length($1)/2;
      key          = $2;
      value        = $3;

      # No prefix or parent for the top level (indent zero)
      root_prefix  = "'$prefix'_";
      if (indent ==0 ) {
        prefix = "";          parent_key = "'$prefix'";
      } else {
        prefix = root_prefix; parent_key = keys[indent-1];
      }

      keys[indent] = key;

      # remove keys left behind if prior row was indented more than this row
      for (i in keys) {if (i > indent) {delete keys[i]}}

      if (length(value) > 0) {
         # value
         printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
         printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
      } else {
         # collection
         printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
         printf("declare -g -A %s%s;\n", root_prefix, key);
         printf("%s%s[parent]=\"%s%s\";\n", root_prefix, key, prefix, parent_key);
      }
   }'
}

# helper to load yay data file
yay() { eval $(yay_parse "$@"); }

В связанном исходном файле есть некоторая документация, и ниже приводится краткое объяснение того, что делает код.

yay_parseФункция сначала локализует inputфайл или завершает работу со статусом выхода 1. Затем, она определяет набор данных prefix, либо явно заданный или полученный из имени файла.

Он записывает действительные bashкоманды в свой стандартный вывод, который, если выполняется, определяет массивы, представляющие содержимое файла входных данных. Первый из них определяет массив верхнего уровня:

echo "declare -g -A $prefix;"

Обратите внимание, что объявления массива являются ассоциативными ( -A), что является функцией Bash версии 4. Объявления также являются глобальными ( -g), поэтому они могут выполняться в функции, но быть доступными для глобальной области видимости, такой как yayхелпер:

yay() { eval $(yay_parse "$@"); }

Исходные данные изначально обрабатываются с помощью sed. Он удаляет строки, которые не соответствуют спецификации формата Yamlesque, перед тем как разграничить допустимые поля Yamlesque символом- разделителем файлов ASCII и удалить все двойные кавычки, окружающие поле значения.

 local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
 sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
        -e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |

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

Файл Сепаратор (28 / гекс 12 / восьмеричные 034) используются , потому что, как непечатаемый характер, то маловероятно, что во входных данных.

Результат передается по awkодной строке за раз. Он использует символ FS для назначения каждого поля переменной:

indent       = length($1)/2;
key          = $2;
value        = $3;

Все строки имеют отступ (возможно, ноль) и ключ, но не у всех есть значение. Он вычисляет уровень отступа для строки, разделяющей длину первого поля, которое содержит начальный пробел, на два. Элементы верхнего уровня без отступа находятся на нулевом уровне отступа.

Далее выясняется, что prefixиспользовать для текущего элемента. Это то, что добавляется к имени ключа для создания имени массива. Существует root_prefixмассив верхнего уровня, который определяется как имя набора данных и подчеркивание:

root_prefix  = "'$prefix'_";
if (indent ==0 ) {
  prefix = "";          parent_key = "'$prefix'";
} else {
  prefix = root_prefix; parent_key = keys[indent-1];
}

parent_keyКлюч на уровне отступа выше уровня отступа текущей строки и представляет собой коллекцию , что текущая строка является частью. Пары ключ / значение коллекции будут храниться в массиве, имя которого определено как объединение prefixи parent_key.

Для верхнего уровня (нулевого уровня отступа) префикс набора данных используется в качестве родительского ключа, поэтому у него нет префикса (он установлен в ""). Все остальные массивы имеют префикс root.

Затем текущий ключ вставляется в (awk-internal) массив, содержащий ключи. Этот массив сохраняется в течение всего сеанса awk и поэтому содержит ключи, вставленные предыдущими строками. Ключ вставляется в массив, используя его отступ в качестве индекса массива.

keys[indent] = key;

Поскольку этот массив содержит ключи из предыдущих строк, удаляются все ключи с уровнем отступа, меньшим, чем уровень отступа текущей строки:

 for (i in keys) {if (i > indent) {delete keys[i]}}

Это оставляет массив ключей, содержащий цепочку ключей, от корня на уровне отступа 0 до текущей строки. Он удаляет устаревшие ключи, которые остаются, когда предыдущая строка была смещена глубже текущей строки.

В последнем разделе выводятся bashкоманды: строка ввода без значения начинает новый уровень отступа ( коллекция на языке YAML), а строка ввода со значением добавляет ключ к текущей коллекции.

Имя коллекции - это конкатенация текущей строки prefixи parent_key.

Когда ключ имеет значение, ключ с этим значением присваивается текущей коллекции следующим образом:

printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);

Первый оператор выводит команду для присвоения значения элементу ассоциативного массива, названного в честь ключа, а второй выводит команду для добавления ключа в keysсписок разделенных пробелами коллекции :

<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";

Когда ключ не имеет значения, новая коллекция запускается так:

printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);

Первый оператор выводит команду для добавления новой коллекции в childrenсписок разделенных пробелами текущей коллекции, а второй выводит команду для объявления нового ассоциативного массива для новой коллекции:

<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;

Все выходные данные yay_parseмогут быть проанализированы как команды bash с помощью команд bash evalили sourceвстроенных команд.


Рассматривали ли вы сделать этот проект на GitHub? Или это уже?
Даниэль

@daniel, он есть в GitHub, но не в своем репо - вы можете найти его здесь . Смотрите examplesи usr/libкаталоги, Они связаны в моем ответе на вопрос. Если есть интерес, я мог бы разбить его в своем собственном репо.
звездный день

4
Слава на YAY. Сначала я переписал его как чистый bash, но потом я не мог остановиться и переопределил его как базовый синтаксический анализатор с поддержкой массивов и вложенных структур, которые не могут наступать на имена друг друга. Это на github.com/binaryphile/y2s .
Бинарный

5
perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh

полезно только для плоских конфигураций. это не применимо для структурированного yaml. другое, как предотвратить использование временного файла .sh?
Зник

5

Другой вариант - преобразовать YAML в JSON, а затем использовать jq для взаимодействия с представлением JSON либо для извлечения из него информации, либо для ее редактирования.

Я написал простой скрипт bash, который содержит этот клей - см. Проект Y2J на GitHub


2

Если вам нужно одно значение, вы можете использовать инструмент, который преобразует ваш документ YAML в JSON и передает jq, например, в yq.

Содержание sample.yaml:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples
  thing:
    cats: oranges

Пример:

$ yq -r '.bob["thing"]["cats"]' sample.yaml 
oranges

1

Я знаю, что это очень конкретно, но я думаю, что мой ответ может быть полезен для определенных пользователей.
Если у вас есть nodeи npmустановлены на вашем компьютере, вы можете использовать js-yaml.
Первая установка:

npm i -g js-yaml
# or locally
npm i js-yaml

тогда в вашем скрипте bash

#!/bin/bash
js-yaml your-yaml-file.yml

Также, если вы используете, jqвы можете сделать что-то подобное

#!/bin/bash
json="$(js-yaml your-yaml-file.yml)"
aproperty="$(jq '.apropery' <<< "$json")"
echo "$aproperty"

Потому что js-yamlпреобразует файл yaml в строковый литерал json. Затем вы можете использовать строку с любым парсером json в вашей системе Unix.


1

Если у вас есть Python 2 и PyYAML, вы можете использовать этот парсер, который я написал, который называется parse_yaml.py . Некоторые из более простых вещей, которые он делает, позволяют вам выбрать префикс (в случае, если у вас более одного файла с одинаковыми переменными) и выбрать одно значение из файла yaml.

Например, если у вас есть эти файлы yaml:

staging.yaml:

db:
    type: sqllite
    host: 127.0.0.1
    user: dev
    password: password123

prod.yaml:

db:
    type: postgres
    host: 10.0.50.100
    user: postgres
    password: password123

Вы можете загрузить оба без конфликта.

$ eval $(python parse_yaml.py prod.yaml --prefix prod --cap)
$ eval $(python parse_yaml.py staging.yaml --prefix stg --cap)
$ echo $PROD_DB_HOST
10.0.50.100
$ echo $STG_DB_HOST
127.0.0.1

И даже вишня выбирает нужные вам ценности.

$ prod_user=$(python parse_yaml.py prod.yaml --get db_user)
$ prod_port=$(python parse_yaml.py prod.yaml --get db_port --default 5432)
$ echo prod_user
postgres
$ echo prod_port
5432

1

Вы могли бы использовать эквивалент в YQ , что написано в golang:

./go-yg -yamlFile /home/user/dev/ansible-firefox/defaults/main.yml -key
firefox_version

возвращает:

62.0.3

0

Вы также можете рассмотреть возможность использования Grunt (JavaScript Task Runner). Может быть легко интегрирован с оболочкой. Он поддерживает чтение файлов YAML ( grunt.file.readYAML) и JSON ( grunt.file.readJSON).

Это может быть достигнуто путем создания задачи в Gruntfile.js(или Gruntfile.coffee), например:

module.exports = function (grunt) {

    grunt.registerTask('foo', ['load_yml']);

    grunt.registerTask('load_yml', function () {
        var data = grunt.file.readYAML('foo.yml');
        Object.keys(data).forEach(function (g) {
          // ... switch (g) { case 'my_key':
        });
    });

};

затем из оболочки просто запустите grunt foo(проверьте grunt --helpналичие доступных задач).

Более того, вы можете реализовать exec:footasks ( grunt-exec) с входными переменными, передаваемыми из task ( foo: { cmd: 'echo bar <%= foo %>' }), чтобы напечатать вывод в любом формате, который вам нужен, а затем передать его в другую команду.


Существует также инструмент, похожий на Grunt, он называется gulp с дополнительным плагином gulp-yaml .

Установить через: npm install --save-dev gulp-yaml

Пример использования:

var yaml = require('gulp-yaml');

gulp.src('./src/*.yml')
  .pipe(yaml())
  .pipe(gulp.dest('./dist/'))

gulp.src('./src/*.yml')
  .pipe(yaml({ space: 2 }))
  .pipe(gulp.dest('./dist/'))

gulp.src('./src/*.yml')
  .pipe(yaml({ safe: true }))
  .pipe(gulp.dest('./dist/'))

Чтобы узнать больше о формате YAML , посетите сайт YAML, чтобы найти доступные проекты, библиотеки и другие ресурсы, которые помогут вам разобрать этот формат.


Другие инструменты:

  • Jshon

    разбирает, читает и создает JSON


0

Я знаю, что мой ответ конкретный, но если на одном уже установлены PHP и Symfony , может быть очень удобно использовать YAML-парсер Symfony.

Например:

php -r "require '$SYMFONY_ROOT_PATH/vendor/autoload.php'; \
    var_dump(\Symfony\Component\Yaml\Yaml::parse(file_get_contents('$YAML_FILE_PATH')));"

Здесь я просто использовал var_dumpдля вывода анализируемый массив, но, конечно, вы можете сделать гораздо больше ... :)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.