¡Acabo de escribir un analizador que llamé Yay! (¡ Yaml no es Yamlesque! ) Que analiza Yamlesque , un pequeño subconjunto de YAML. Entonces, si está buscando un analizador YAML 100% compatible para Bash, entonces este no lo es. Sin embargo, para citar el OP, si desea un archivo de configuración estructurado que sea lo más fácil posible para un usuario no técnico para editar que sea similar a YAML, esto puede ser de interés.
Está inspirado en la respuesta anterior, pero escribe matrices asociativas ( sí, requiere Bash 4.x ) en lugar de variables básicas. Lo hace de una manera que permite analizar los datos sin el conocimiento previo de las claves para que se pueda escribir el código controlado por datos.
Además de los elementos de la matriz de clave / valor, cada matriz tiene una keys
matriz que contiene una lista de nombres de clave, una children
matriz que contiene nombres de matrices secundarias y una parent
clave que se refiere a su padre.
Este es un ejemplo de 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
Aquí hay un ejemplo que muestra cómo usarlo:
#!/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
que salidas:
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
}
}
Y aquí está el analizador:
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 "$@"); }
Hay algo de documentación en el archivo fuente vinculado y a continuación hay una breve explicación de lo que hace el código.
La yay_parse
función primero localiza el input
archivo o sale con un estado de salida de 1. Luego, determina el conjunto de datos prefix
, ya sea explícitamente especificado o derivado del nombre del archivo.
Escribe bash
comandos válidos en su salida estándar que, si se ejecutan, definen matrices que representan el contenido del archivo de datos de entrada. El primero de ellos define la matriz de nivel superior:
echo "declare -g -A $prefix;"
Tenga en cuenta que las declaraciones de matriz son asociativas ( -A
), que es una característica de Bash versión 4. Las declaraciones también son globales ( -g
), por lo que pueden ejecutarse en una función pero estar disponibles para el alcance global como el yay
ayudante:
yay() { eval $(yay_parse "$@"); }
Los datos de entrada se procesan inicialmente con sed
. Descarta líneas que no coinciden con la especificación de formato Yamlesque antes de delimitar los campos válidos de Yamlesque con un carácter de separador de archivo ASCII y eliminar las comillas dobles que rodean el campo de valor.
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" |
Las dos expresiones son similares; difieren solo porque el primero selecciona los valores entre comillas, mientras que el segundo selecciona los que no están entre comillas.
El separador de archivos (28 / hex 12 / octal 034) se utiliza porque, como carácter no imprimible, es poco probable que esté en los datos de entrada.
El resultado se canaliza en el awk
que procesa su entrada una línea a la vez. Utiliza el FS caracteres para asignar cada campo a una variable:
indent = length($1)/2;
key = $2;
value = $3;
Todas las líneas tienen una sangría (posiblemente cero) y una clave, pero no todas tienen un valor. Calcula un nivel de sangría para la línea que divide la longitud del primer campo, que contiene el espacio en blanco inicial, por dos. Los elementos de nivel superior sin sangría están en el nivel de sangría cero.
A continuación, determina qué prefix
usar para el elemento actual. Esto es lo que se agrega al nombre de una clave para crear un nombre de matriz. Hay una root_prefix
para la matriz de nivel superior que se define como el nombre de conjunto de datos y un carácter de subrayado:
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
La parent_key
es la clave en el nivel de sangría por encima del nivel de sangría de la línea actual y representa la colección de la que forma parte la línea actual. Los pares clave / valor de la colección se almacenarán en una matriz con su nombre definido como la concatenación de prefix
y parent_key
.
Para el nivel superior (nivel de sangría cero), el prefijo del conjunto de datos se utiliza como clave principal, por lo que no tiene prefijo (está establecido en ""
). Todos los demás arreglos tienen el prefijo raíz.
A continuación, la clave actual se inserta en una matriz (awk-internal) que contiene las claves. Esta matriz persiste durante toda la sesión awk y, por lo tanto, contiene claves insertadas por líneas anteriores. La clave se inserta en la matriz usando su sangría como índice de la matriz.
keys[indent] = key;
Debido a que esta matriz contiene claves de líneas anteriores, se eliminan todas las claves con un nivel de sangría mayor que el nivel de sangría de la línea actual:
for (i in keys) {if (i > indent) {delete keys[i]}}
Esto deja la matriz de claves que contiene el llavero desde la raíz en el nivel de sangría 0 a la línea actual. Elimina las claves obsoletas que permanecen cuando la línea anterior se sangra más profundo que la línea actual.
La sección final genera los bash
comandos: una línea de entrada sin un valor inicia un nuevo nivel de sangría (una colección en lenguaje YAML) y una línea de entrada con un valor agrega una clave a la colección actual.
El nombre de la colección es la concatenación de la línea actual prefix
y parent_key
.
Cuando una clave tiene un valor, una clave con ese valor se asigna a la colección actual de esta manera:
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
La primera instrucción genera el comando para asignar el valor a un elemento de matriz asociativo nombrado después de la clave y la segunda genera el comando para agregar la clave a la keys
lista delimitada por espacios de la colección :
<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";
Cuando una clave no tiene un valor, se inicia una nueva colección como esta:
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
La primera instrucción genera el comando para agregar la nueva colección a la children
lista delimitada por espacios de la colección actual y la segunda genera el comando para declarar una nueva matriz asociativa para la nueva colección:
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
Toda la salida de yay_parse
puede ser analizada como comandos bash por los comandos bash eval
o source
integrados.