Ich habe gerade einen Parser geschrieben, den ich Yay! ( Yaml ist nicht Yamlesque! ), Das Yamlesque analysiert , eine kleine Untergruppe von YAML. Wenn Sie also nach einem 100% kompatiblen YAML-Parser für Bash suchen, ist dies nicht der Fall. Um das OP zu zitieren: Wenn Sie eine strukturierte Konfigurationsdatei wünschen, die für einen nicht technischen Benutzer so einfach wie möglich zu bearbeiten ist und die YAML-ähnlich ist, kann dies von Interesse sein.
Es ist von der früheren Antwort geprägt , schreibt aber assoziative Arrays ( ja, es erfordert Bash 4.x ) anstelle von Basisvariablen. Dies geschieht so, dass die Daten ohne vorherige Kenntnis der Schlüssel analysiert werden können, sodass datengesteuerter Code geschrieben werden kann.
Neben den Schlüssel- / Wert-Array-Elementen verfügt jedes Array über ein keys
Array mit einer Liste von Schlüsselnamen, ein children
Array mit Namen von untergeordneten Arrays und einen parent
Schlüssel, der auf das übergeordnete Array verweist.
Dies ist ein Beispiel für 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
Hier ist ein Beispiel, das zeigt, wie man es benutzt:
#!/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
welche Ausgänge:
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
}
}
Und hier ist der Parser:
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 "$@"); }
Die verknüpfte Quelldatei enthält einige Dokumentationen. Nachfolgend finden Sie eine kurze Erläuterung der Funktionsweise des Codes.
Die yay_parse
Funktion sucht zuerst die input
Datei oder beendet sie mit einem Exit-Status von 1. Anschließend ermittelt sie das Dataset prefix
, das entweder explizit angegeben oder vom Dateinamen abgeleitet ist.
Es schreibt gültige bash
Befehle in seine Standardausgabe, die bei Ausführung Arrays definieren, die den Inhalt der Eingabedatendatei darstellen. Die erste davon definiert das Array der obersten Ebene:
echo "declare -g -A $prefix;"
Beachten Sie, dass Array-Deklarationen assoziativ ( -A
) sind, was eine Funktion von Bash Version 4 ist. Deklarationen sind ebenfalls global ( -g
), sodass sie in einer Funktion ausgeführt werden können, aber für den globalen Bereich wie der yay
Helfer verfügbar sind :
yay() { eval $(yay_parse "$@"); }
Die Eingabedaten werden zunächst mit verarbeitet sed
. Es fällt Zeilen , die mit einem ASCII vor abgrenzt die gültigen Yamlesque Felder nicht die Formatspezifikation Yamlesque entsprechen Datei Separator Zeichen und Entfernen aller doppelten Anführungszeichen rund um das Wertefeld.
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" |
Die beiden Ausdrücke sind ähnlich; Sie unterscheiden sich nur, weil der erste zitierte Werte auswählt, während der zweite nicht zitierte Werte auswählt.
Der Dateitrenner (28 / hex 12 / octal 034) wird verwendet, da er als nicht druckbares Zeichen wahrscheinlich nicht in den Eingabedaten enthalten ist.
Das Ergebnis wird weitergeleitet, in awk
das die Eingabe zeilenweise verarbeitet wird. Es verwendet das FS- Zeichen, um jedes Feld einer Variablen zuzuweisen:
indent = length($1)/2;
key = $2;
value = $3;
Alle Zeilen haben einen Einzug (möglicherweise Null) und einen Schlüssel, aber nicht alle haben einen Wert. Es wird eine Einrückungsstufe für die Linie berechnet, die die Länge des ersten Felds, das das führende Leerzeichen enthält, durch zwei teilt. Die Elemente der obersten Ebene ohne Einzug befinden sich auf der Einzugsebene Null.
Als nächstes wird herausgefunden, was prefix
für das aktuelle Element verwendet werden soll. Dies wird einem Schlüsselnamen hinzugefügt, um einen Array-Namen zu erstellen. Es gibt ein root_prefix
Array für die oberste Ebene, das als Datensatzname und Unterstrich definiert ist:
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
Das parent_key
ist der Schlüssel auf der Einrückungsstufe über der Einrückungsstufe der aktuellen Zeile und repräsentiert die Sammlung, zu der die aktuelle Zeile gehört. Die Schlüssel / Wert-Paare der Sammlung werden in einem Array gespeichert, dessen Name als Verkettung von prefix
und definiert ist parent_key
.
Für die oberste Ebene (Einzugsebene Null) wird das Datensatzpräfix als übergeordneter Schlüssel verwendet, sodass es kein Präfix hat (es ist auf gesetzt ""
). Allen anderen Arrays wird das Root-Präfix vorangestellt.
Als nächstes wird der aktuelle Schlüssel in ein (awk-internes) Array eingefügt, das die Schlüssel enthält. Dieses Array bleibt während der gesamten awk-Sitzung bestehen und enthält daher Schlüssel, die durch vorherige Zeilen eingefügt wurden. Der Schlüssel wird mit seinem Einzug als Array-Index in das Array eingefügt.
keys[indent] = key;
Da dieses Array Schlüssel aus vorherigen Zeilen enthält, werden alle Schlüssel entfernt, deren Einrückungsstufe größer als die Einrückungsstufe der aktuellen Zeile ist:
for (i in keys) {if (i > indent) {delete keys[i]}}
Dadurch bleibt das Schlüsselarray mit der Schlüsselkette vom Stamm auf Einrückungsstufe 0 bis zur aktuellen Zeile. Es werden veraltete Schlüssel entfernt, die verbleiben, wenn die vorherige Zeile tiefer als die aktuelle Zeile eingerückt wurde.
Der letzte Abschnitt gibt die bash
Befehle aus: Eine Eingabezeile ohne Wert startet eine neue Einrückungsstufe (eine Sammlung im YAML-Sprachgebrauch) und eine Eingabezeile mit einem Wert fügt der aktuellen Sammlung einen Schlüssel hinzu.
Der Name der Sammlung ist die Verkettung der aktuellen Zeilen prefix
und parent_key
.
Wenn ein Schlüssel einen Wert hat, wird der aktuellen Sammlung ein Schlüssel mit diesem Wert wie folgt zugewiesen:
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
Die erste Anweisung gibt den Befehl zum Zuweisen des Werts zu einem nach dem Schlüssel benannten assoziativen Array-Element aus, und die zweite gibt den Befehl zum Hinzufügen des Schlüssels zur durch Leerzeichen getrennten keys
Liste der Sammlung aus :
<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";
Wenn ein Schlüssel keinen Wert hat, wird eine neue Sammlung wie folgt gestartet:
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
Die erste Anweisung gibt den Befehl zum Hinzufügen der neuen Sammlung zur durch Leerzeichen getrennten children
Liste der aktuellen Sammlung aus, und die zweite gibt den Befehl zum Deklarieren eines neuen assoziativen Arrays für die neue Sammlung aus:
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
Die gesamte Ausgabe von yay_parse
kann von den Bash- eval
oder source
integrierten Befehlen als Bash-Befehle analysiert werden.