Я щойно написав аналізатор, який я назвав 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
вбудованих команд.