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