我刚刚编写了一个解析器,我称之为 Yay!(Yaml 不是 Yamlesque!),它解析 Yamlesque,它是YAML。因此,如果您正在为 Bash 寻找 100% 兼容的 YAML 解析器,那么这不是它。但是,引用 OP,如果您想要一个结构化的配置文件,该文件对于非技术用户来说尽可能容易编辑,它类似于 YAML,这可能会引起您的兴趣。
它是 inspred by the earlier answer,但写入关联数组(是的,它需要 Bash 4.x)而不是基本变量。这样做的方式是允许在事先不知道键的情况下解析数据,以便编写数据驱动的代码。
除了键/值数组元素之外,每个数组还有一个包含键名称列表的keys 数组、一个包含子数组名称的children 数组和一个引用其父数组的parent 键。
This是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
Here 是一个展示如何使用它的示例:
#!/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
}
}
而here 是解析器:
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),因此它们可以在函数中执行,但可以在全局范围内使用,例如 @ 987654344@帮手:
yay() { eval $(yay_parse "$@"); }
输入数据最初使用sed 处理。在使用 ASCII File Separator 字符分隔有效的 Yamlesque 字段并删除值字段周围的所有双引号之前,它会删除与 Yamlesque 格式规范不匹配的行。
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" |
这两种表达方式相似;它们的不同之处仅在于第一个选择引用的值,而第二个选择未引用的值。
之所以使用File Separator (28/hex 12/octal 034),是因为作为不可打印字符,它不太可能出现在输入数据中。
结果被传送到awk,它一次处理一行输入。它使用FS 字符将每个字段分配给一个变量:
indent = length($1)/2;
key = $2;
value = $3;
所有行都有一个缩进(可能为零)和一个键,但它们并不都有值。它计算将包含前导空格的第一个字段的长度除以 2 的行的缩进级别。没有任何缩进的顶级项目的缩进级别为零。
接下来,它会计算出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 的根到当前行的密钥链的 keys 数组。当前一行缩进比当前行更深时,它会删除保留的陈旧键。
最后一部分输出bash 命令:没有值的输入行开始一个新的缩进级别(YAML 用语中的 collection),一个有值的输入行将一个键添加到当前收藏。
集合的名称是当前行的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 eval 或 source 内置命令解析为 bash 命令。