Je viens d'écrire un analyseur que j'ai appelé Yay! ( Yaml n'est pas Yamlesque! ) Qui analyse Yamlesque , un petit sous-ensemble de YAML. Donc, si vous recherchez un analyseur YAML 100% conforme pour Bash, ce n'est pas ça. Cependant, pour citer l'OP, si vous voulez un fichier de configuration structuré qui soit aussi facile que possible pour un utilisateur non technique à éditer qui soit de type YAML, cela peut être intéressant.
Il est inséré par la réponse précédente mais écrit des tableaux associatifs ( oui, il nécessite Bash 4.x ) au lieu de variables de base. Il le fait d'une manière qui permet aux données d'être analysées sans connaissance préalable des clés afin que le code basé sur les données puisse être écrit.
En plus des éléments du tableau clé / valeur, chaque tableau a un keys
tableau contenant une liste de noms de clés, un children
tableau contenant les noms des tableaux enfants et une parent
clé qui fait référence à son parent.
Voici un exemple 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
Voici un exemple montrant comment l'utiliser:
#!/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
qui sort:
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
}
}
Et voici l'analyseur:
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 "$@"); }
Il y a une documentation dans le fichier source lié et ci-dessous est une brève explication de ce que fait le code.
La yay_parse
fonction localise d'abord le input
fichier ou se termine avec un statut de sortie de 1. Ensuite, elle détermine l'ensemble de données prefix
, soit explicitement spécifié soit dérivé du nom de fichier.
Il écrit des bash
commandes valides dans sa sortie standard qui, si elles sont exécutées, définissent des tableaux représentant le contenu du fichier de données d'entrée. Le premier de ceux-ci définit le tableau de niveau supérieur:
echo "declare -g -A $prefix;"
Notez que les déclarations de tableau sont associatives ( -A
), ce qui est une fonctionnalité de Bash version 4. Les déclarations sont également globales ( -g
) afin qu'elles puissent être exécutées dans une fonction mais être disponibles pour la portée globale comme l' yay
aide:
yay() { eval $(yay_parse "$@"); }
Les données d'entrée sont initialement traitées avec sed
. Il supprime les lignes qui ne correspondent pas à la spécification de format Yamlesque avant de délimiter les champs Yamlesque valides avec un caractère séparateur de fichier ASCII et de supprimer les guillemets doubles entourant le champ de valeur.
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" |
Les deux expressions sont similaires; ils diffèrent uniquement parce que le premier choisit les valeurs cotées alors que le second choisit les valeurs non cotées.
Le séparateur de fichiers (28 / hex 12 / octal 034) est utilisé car, en tant que caractère non imprimable, il est peu probable qu'il se trouve dans les données d'entrée.
Le résultat est acheminé dans awk
lequel traite son entrée une ligne à la fois. Il utilise le caractère FS pour affecter chaque champ à une variable:
indent = length($1)/2;
key = $2;
value = $3;
Toutes les lignes ont un retrait (éventuellement zéro) et une clé mais elles n'ont pas toutes une valeur. Il calcule un niveau d'indentation pour la ligne divisant la longueur du premier champ, qui contient l'espace blanc de début, par deux. Les éléments de niveau supérieur sans aucun retrait sont au niveau de retrait zéro.
Ensuite, il détermine ce qu'il prefix
faut utiliser pour l'élément actuel. C'est ce qui est ajouté à un nom de clé pour créer un nom de tableau. Il y a un root_prefix
pour le tableau de niveau supérieur qui est défini comme le nom de l'ensemble de données et un trait de soulignement:
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
Le parent_key
est la clé au niveau de retrait au-dessus du niveau de retrait de la ligne actuelle et représente la collection dont la ligne actuelle fait partie. Les paires clé / valeur de la collection seront stockées dans un tableau avec son nom défini comme la concaténation de prefix
et parent_key
.
Pour le niveau supérieur (retrait de niveau zéro), le préfixe de l'ensemble de données est utilisé comme clé parente, il n'a donc pas de préfixe (il est défini sur ""
). Tous les autres tableaux sont précédés du préfixe racine.
Ensuite, la clé actuelle est insérée dans un tableau (awk-internal) contenant les clés. Ce tableau persiste pendant toute la session awk et contient donc des clés insérées par les lignes précédentes. La clé est insérée dans le tableau en utilisant son retrait comme index du tableau.
keys[indent] = key;
Étant donné que ce tableau contient des clés des lignes précédentes, toutes les clés avec un niveau d'indentation supérieur au niveau d'indentation de la ligne actuelle sont supprimées:
for (i in keys) {if (i > indent) {delete keys[i]}}
Cela laisse le tableau de clés contenant le porte-clés de la racine au niveau d'indentation 0 à la ligne courante. Il supprime les clés obsolètes qui restent lorsque la ligne précédente était plus profonde que la ligne actuelle.
La dernière section produit les bash
commandes: une ligne d'entrée sans valeur démarre un nouveau niveau d'indentation (une collection en langage YAML) et une ligne d'entrée avec une valeur ajoute une clé à la collection actuelle.
Le nom de la collection est la concaténation des lignes actuelles prefix
et parent_key
.
Lorsqu'une clé a une valeur, une clé avec cette valeur est affectée à la collection actuelle comme ceci:
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
La première instruction génère la commande pour affecter la valeur à un élément de tableau associatif nommé d'après la clé et la seconde renvoie la commande pour ajouter la clé à la keys
liste délimitée par des espaces de la collection :
<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";
Lorsqu'une clé n'a pas de valeur, une nouvelle collection est démarrée comme ceci:
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
La première instruction renvoie la commande pour ajouter la nouvelle collection à la children
liste délimitée par des espaces de la collection actuelle et la seconde renvoie la commande pour déclarer un nouveau tableau associatif pour la nouvelle collection:
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
Toute la sortie de yay_parse
peut être analysée en tant que commandes bash par les commandes bash eval
ou source
intégrées.