Як я можу розібрати файл YAML зі скрипту оболонки Linux?


193

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


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

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

@kenorb JSON! = yml / YAML
swe

Я виявив тісно пов'язані функції github pkuczynski, з яких найкращим (для мене) є те, що з ясперів, що підтримуються у його власному github
splaisan

Відповіді:


56

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

Мені потрібно залучити деякі YAML як bash змінні. YAML ніколи не буде більш ніж на рівні рівня.

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

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

Вихід на зразок dis:

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
Ямл на рівні одного рівня має багато форм - значення можна розділити на наступний відступ; значення можуть бути цитовані декількома способами, оболонка не буде розбиратися; все може бути написано на одній лінії з фігурними дужками: {KEY: 'value', ...}; і, можливо, інші. Найголовніше, якщо ви маєте намір оцінити результат як код оболонки, це буде дуже небезпечно.
Бені Чернявський-Паскін

281

Ось лише аналізатор, що використовує лише баш, який використовує 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, згенеровані рубіном, які можуть включати символи рубіну, наприклад:

---
: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
Класний Стефан! Було б дивовижно, якби це могло перетворити -позначення ямлів і в рідні масиви bash!
quickshiftin

3
Це зробити досить просто, якщо ви зміните рядок printf в сценарії awk. Зауважте, що в тому, що bash не підтримує багатовимірні асоціативні масиви, тож ви закінчуєте масив + один ключ на значення. Гм, мабуть, варто перенести це на github ...
Стефан Фарестам

5
Тут очікується стандартний відступ yml з 2 пробілів. Якщо ви використовуєте 4 пробіли, то змінні отримають дві підкреслення як роздільник, наприклад global__debugзамість global_debug.
k0pernikus

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

3
Він друкує лише вихід на екрані. Як ви отримаєте доступ до значень згодом?
сат

96

Я написав shyamlpython для запитів YAML в командному рядку shell.

Огляд:

$ 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.

Більше зразків та документації доступні на сторінці gym shyaml або на shyaml PyPI .


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

@ JiminyCricket Будь ласка, використовуйте сторінку випуску github! Я був би радий принаймні це слідкувати. ;)
вааб

1
На жаль, shyamlсмішно повільно
nowox

43

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

йому просто бракує можливостей фільтрації
Антонін

formulae.brew.sh/formula/yq має 26679 встановити за останній рік.
dustinevan

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 відлунням до перекладача рубіну. але як слід використовувати цю змінну під рештою скрипту bash?
Znik

Так, це корисно. RUBY_SCRIPTМінлива рубіновий скрипт , який може бути записаний в файл замість (бігти з ruby -ryaml <rubyscript_filename>). Він містить логіку перетворення вхідного тексту в деякий вихідний текст, внутрішньо зберігаючи вміст у dataзмінну. Ехо виводить текст yaml, але ви можете використовувати його cat <yaml_filename>для передачі вмісту файлу.
Рафаель

Вибачте, але я не бачу цього в прикладі вище. Спочатку змінна RUBY_SCRIPT зберігає код для рубінного перекладача. Далі echo -e імітує будь-які дані yaml, це за допомогою палі, перенаправленої в інтерпретатор рубіну. Це називає рубіновий код як вбудований скрипт і, нарешті, друкує для виведення прикладів змінних 'a' та 'b'. Тоді де мінлива завантаження в bash для його виконавчого виконуваного коду? Я бачу лише один спосіб вирішення. поклавши вихідний рубін у тимчасовий файл, який повинен конувати рядки: змінний = 'значення', а після цього завантажувати його в bash на '. тимчасовий_файл '. але це рішення, а не дозвіл.
Znik

1
@Znik після того, як у вас є щось на stdout, вироблене чимось, що подається stdin, решта покладається в руки кодера bash (і як нагадування, якщо вам потрібно stdoutпередати змінну, вам не доведеться покладатися на тимчасові файли! використання x=$(...)або навіть read a b c < <(...)). Отже, це правильне рішення, коли ви точно знаєте, що ви хочете отримати у файлі YAML, і знаєте, як записати лінії рубіну для доступу до цих даних. Навіть якщо вона груба, це повний доказ концепції ідеї ІМХО. Однак це правда, що це не дає вам повного абстрагування.
vaab

Так. Ти ріг. Дякую вам за цю хитрість. Використовувати одну змінну просто. але багато бойових дій - ні. трюк з переліченим списком змінних <<(виконання для stdout) дуже корисний :)
Znik

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/PyYAMLDдокументація
Джордан Стюарт

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, ...}до регулярної та перетворює їх у більш простий стиль ямла. Другий sedвиклик робить те ж саме для короткого позначення списків і перетворюється [ entry, ... ]на деталізований список з -позначеннями. Третій sedвиклик є оригінальним, який обробляв звичайні словники, тепер додається для обробки списків із -відступами. awkЧастина вводить індекс для кожного рівня відступу і збільшує його , коли ім'я змінної порожньо (тобто при обробці списку). Поточне значення лічильників використовується замість порожнього vname. Піднімаючись на один рівень, лічильники нулюються.

Редагувати: я створив для цього сховище github .


11

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


11

Я щойно написав аналізатор, який я назвав Yay! ( Ямл - не Ямск! ), Який розбирає Ямлеск , невелику підмножину ЯМЛ. Отже, якщо ви шукаєте 100% сумісний аналізатор YAML для Bash, це не все. Однак, цитуючи ОП, якщо ви хочете, щоб структурований файл конфігурації, який непростий для редагування нетехнічного користувача редагував YAML, це може зацікавити.

Це натхнене попередньою відповіддю, але записує асоціативні масиви ( так, він вимагає Bash 4.x ) замість основних змінних. Це робиться таким чином, що дозволяє аналізувати дані без попереднього знання клавіш, щоб можна було записати керований даними код.

Як і елементи масиву ключ / значення, кожен масив має keysмасив, що містить перелік імен ключів, childrenмасив, що містить імена дочірніх масивів, і parentключ, який посилається на його батьківський.

Це приклад Ямлеська:

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. Він випадає рядків, які не відповідають специфікації формату Ямск, перед тим як розмежувати дійсні поля Ямлеск символом роздільника файлів 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.

Для верхнього рівня (нульовий рівень відступу) префікс набору даних використовується як батьківський ключ, тому в ньому немає префікса (встановлено значення ""). Усі інші масиви мають префікс із кореневим префіксом.

Далі, поточний ключ вставляється у (awk-внутрішній) масив, що містить ключі. Цей масив зберігається протягом усього сеансу 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каталоги. Вони пов'язані у моїй відповіді на запитання. Якщо є інтерес, я міг би розбити його на власне репо.
starfry

4
Кудо на YAY. Спочатку я переписав це на чистий баш, але потім я не міг зупинитися і повторно застосував його як базовий аналізатор з підтримкою масивів та вкладених структур, які не можуть наступати на імена інших. Це за адресою github.com/binaryphile/y2s .
Бінарний Філі

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

корисна лише для плоских конфігурацій. він не застосовується для структурованого ямлу. інше, як запобігти використанню тимчасової file.sh?
Znik

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

то у вашому баш-скрипті

#!/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:fooзавдання ( grunt-exec) з вхідними змінними, переданими з вашої задачі ( 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.