Wie kann ich eine YAML-Datei aus einem Linux-Shell-Skript analysieren?


192

Ich möchte eine strukturierte Konfigurationsdatei bereitstellen, die für einen nicht technischen Benutzer so einfach wie möglich zu bearbeiten ist (leider muss es sich um eine Datei handeln), und wollte daher YAML verwenden. Ich kann jedoch keine Möglichkeit finden, dies aus einem Unix-Shell-Skript zu analysieren.


Nicht direkt Ihre Frage, aber Sie sollten sich ansible ansehen, wenn es bei Ihrem Shell-Scrriting insbesondere um die Fernverwaltung verschiedener Knoten (und eines Yaml-Inventars) geht
eckes

9
Versuchen Sie yq, Yaml-Dateien in der Shell zu lesen / schreiben. Die Projektseite ist hier: mikefarah.github.io/yq Sie können das Tool mit installieren brew, aptoder laden Sie die binäre. Das Lesen eines Wertes ist so einfach wieyq r some.yaml key.value
vdimitrov

@kenorb JSON = yml / YAML!
swe

Ich fand eng verwandte Funktionen von pkuczynskis Github, von denen die beste (für mich) die von Jasperes war, die in seinem eigenen Github
Splaisan

Antworten:


56

Mein Anwendungsfall kann oder kann nicht ganz der sein, den dieser ursprüngliche Beitrag verlangt hat, aber es ist definitiv ähnlich.

Ich muss einige YAML als Bash-Variablen einfügen. Die YAML wird niemals mehr als eine Ebene tief sein.

YAML sieht so aus:

KEY:                value
ANOTHER_KEY:        another_value
OH_MY_SO_MANY_KEYS: yet_another_value
LAST_KEY:           last_value

Ausgabe wie ein Dis:

KEY="value"
ANOTHER_KEY="another_value"
OH_MY_SO_MANY_KEYS="yet_another_value"
LAST_KEY="last_value"

Ich habe die Ausgabe mit dieser Zeile erreicht:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/gfindet :und ersetzt es durch =", während ignoriert ://(für URLs)
  • s/$/"/gwird "an das Ende jeder Zeile angehängt
  • s/ *=/=/g entfernt alle Leerzeichen vor =

13
Sie sind sich nicht sicher, worauf Sie hinaus wollen, aber wenn Sie meinen, dass dies nicht für alle YAML funktioniert, haben Sie Recht. Deshalb habe ich mit ein paar Qualifikationen eröffnet. Ich habe nur mitgeteilt, was für meinen Anwendungsfall funktioniert hat, da es die Frage besser beantwortet hat als jedes andere zu dieser Zeit. Dies kann definitiv erweitert werden.
Curtis Blackwell

3
ein bisschen offen für Code-Injection, aber wie Sie sagten, ist ein Schritt nach vorne
Oriettaxx

1
Ich habe bisher nur Shell-Skripte geschrieben, die lokal verwendet werden sollen, daher war das für mich kein Problem. Wenn Sie jedoch wissen, wie Sie es sichern können und / oder näher darauf eingehen möchten, wäre ich Ihnen auf jeden Fall dankbar.
Curtis Blackwell

2
Einstufiges, tiefes Yaml hat viele Formen - Werte können auf folgende eingerückte Linie aufgeteilt werden; Werte können auf verschiedene Arten in Anführungszeichen gesetzt werden, die von der Shell nicht analysiert werden. alles kann mit geschweiften Klammern in eine Zeile geschrieben werden : {KEY: 'value', ...}; und möglicherweise andere. Wenn Sie das Ergebnis als Shell-Code auswerten möchten, ist dies vor allem sehr unsicher.
Beni Cherniavsky-Paskin

280

Hier ist ein Nur-Bash-Parser, der sed und awk nutzt, um einfache Yaml-Dateien zu analysieren:

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|^\($s\):|\1|" \
        -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"  $1 |
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]}}
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
      }
   }'
}

Es versteht Dateien wie:

## global definitions
global:
  debug: yes
  verbose: no
  debugging:
    detailed: no
    header: "debugging started"

## output
output:
   file: "yes"

Welche, wenn analysiert mit:

parse_yaml sample.yml

wird ausgegeben:

global_debug="yes"
global_verbose="no"
global_debugging_detailed="no"
global_debugging_header="debugging started"
output_file="yes"

Es versteht auch Yaml-Dateien, die von Ruby generiert wurden und Rubinsymbole enthalten können, wie:

---
:global:
  :debug: 'yes'
  :verbose: 'no'
  :debugging:
    :detailed: 'no'
    :header: debugging started
  :output: 'yes'

und gibt das gleiche wie im vorherigen Beispiel aus.

Typische Verwendung innerhalb eines Skripts ist:

eval $(parse_yaml sample.yml)

parse_yaml akzeptiert ein Präfixargument, sodass alle importierten Einstellungen ein gemeinsames Präfix haben (wodurch das Risiko von Namespace-Kollisionen verringert wird).

parse_yaml sample.yml "CONF_"

Ausbeuten:

CONF_global_debug="yes"
CONF_global_verbose="no"
CONF_global_debugging_detailed="no"
CONF_global_debugging_header="debugging started"
CONF_output_file="yes"

Beachten Sie, dass auf frühere Einstellungen in einer Datei durch spätere Einstellungen verwiesen werden kann:

## global definitions
global:
  debug: yes
  verbose: no
  debugging:
    detailed: no
    header: "debugging started"

## output
output:
   debug: $global_debug

Eine weitere nützliche Verwendung besteht darin, zuerst eine Standarddatei und dann die Benutzereinstellungen zu analysieren. Dies funktioniert, da die letzteren Einstellungen die ersten überschreiben:

eval $(parse_yaml defaults.yml)
eval $(parse_yaml project.yml)

3
Cooler Stefan! Es wäre erstaunlich, wenn es die Yaml- -Notation auch in native Bash-Arrays verwandeln könnte !
Quickshiftin

3
Dies sollte recht einfach sein, wenn Sie die printf-Zeile im awk-Skript ändern. Beachten Sie jedoch, dass Bash keine Unterstützung für mehrdimensionale assoziative Arrays bietet, sodass Sie ein Array + einen einzelnen Schlüssel pro Wert erhalten. Hmm, sollte dies wahrscheinlich auf Github verschieben ...
Stefan Farestam

5
Dies erwartet die standardmäßige yml-Einrückung von 2 Leerzeichen. Wenn Sie 4 Leerzeichen verwenden, erhalten die Variablen zwei Unterstriche als Trennzeichen, z . B. global__debuganstelle von global_debug.
k0pernikus

3
Hi vaab - Obwohl ich sicher bin, dass Sie Recht haben, dass viele Leser echte YAML-Dateien aus der Shell analysieren möchten, ist (zumindest für mich) nicht ganz klar, wie das Ergebnis aussehen würde. Mit diesem Skript habe ich mich mit dem Problem befasst und eine Teilmenge definiert, die eine vernünftige Zuordnung zu Standardvariablen aufweist. Es gibt sicherlich keinen Anspruch darauf, das größere Problem des Parsens realer YAML-Dateien angesprochen zu haben.
Stefan Farestam

3
Es wird nur die Ausgabe auf dem Bildschirm gedruckt. Wie würden Sie später auf die Werte zugreifen?
Sattu

96

Ich habe shyamlin Python für YAML-Abfragebedürfnisse über die Shell-Befehlszeile geschrieben.

Überblick:

$ pip install shyaml      ## installation

Beispiel YAML-Datei (mit komplexen Funktionen):

$ cat <<EOF > test.yaml
name: "MyName !!"
subvalue:
    how-much: 1.1
    things:
        - first
        - second
        - third
    other-things: [a, b, c]
    maintainer: "Valentin Lab"
    description: |
        Multiline description:
        Line 1
        Line 2
EOF

Grundlegende Abfrage:

$ cat test.yaml | shyaml get-value subvalue.maintainer
Valentin Lab

Komplexere Schleifenabfrage für komplexe Werte:

$ cat test.yaml | shyaml values-0 | \
  while read -r -d $'\0' value; do
      echo "RECEIVED: '$value'"
  done
RECEIVED: '1.1'
RECEIVED: '- first
- second
- third'
RECEIVED: '2'
RECEIVED: 'Valentin Lab'
RECEIVED: 'Multiline description:
Line 1
Line 2'

Einige wichtige Punkte:

  • Alle YAML-Typen und Syntax-Kuriositäten werden korrekt behandelt, als mehrzeilige Zeichenfolgen in Anführungszeichen, Inline-Sequenzen ...
  • \0 Die gepolsterte Ausgabe ist für die Manipulation von mehrzeiligen Einträgen verfügbar.
  • einfache gepunktete Notation zur Auswahl von Unterwerten (dh: subvalue.maintainerist ein gültiger Schlüssel).
  • Der Zugriff per Index wird auf Sequenzen bereitgestellt (dh: subvalue.things.-1ist das letzte Element der subvalue.thingsSequenz.)
  • Zugriff auf alle Sequenz- / Strukturelemente auf einmal zur Verwendung in Bash-Schleifen.
  • Sie können den gesamten Teil einer YAML-Datei als ... YAML ausgeben, der sich gut für weitere Manipulationen mit Shyaml eignet.

Weitere Beispiele und Dokumentationen finden Sie auf der Shyaml-Github-Seite oder der Shyaml-PyPI-Seite .


1
Das ist fantastisch! Wäre toll, wenn es ein Flag gäbe, um Yaml-Werte zu ignorieren, die in der Ausgabe leer sind. Im Moment gibt es "null" aus. Ich benutze es zusammen mit envdir, um eine Docker-Compose-Datei an envdir auszugebencat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket

@JiminyCricket Bitte benutzen Sie die Github Issue Seite! Ich würde mich zumindest freuen, dies im Auge zu behalten. ;)
Vaab

1
Leider shyamlist lächerlich langsam
nowox

42

yq ist ein leichter und tragbarer YAML-Befehlszeilenprozessor

Das Ziel des Projekts ist es, das jq oder sed von yaml-Dateien zu sein.

( https://github.com/mikefarah/yq#readme )

Als Beispiel (direkt aus der Dokumentation gestohlen ) mit einer sample.yaml-Datei von:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples

dann

yq r sample.yaml bob.*.cats

wird ausgegeben

- bananas
- apples

Es fehlen nur die Filterfunktionen
Antonin

formulae.brew.sh/formula/yq hat im letzten Jahr 26.679 installiert.
Dustinevan

1
@Antonin Ich bin mir nicht sicher, ob Sie das meinen, aber es sieht so aus, als ob es jetzt einige Filterfunktionen hat: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

Es ist möglich, ein kleines Skript an einige Interpreten wie Python zu übergeben. Eine einfache Möglichkeit, dies mit Ruby und seiner YAML-Bibliothek zu tun, ist die folgende:

$ RUBY_SCRIPT="data = YAML::load(STDIN.read); puts data['a']; puts data['b']"
$ echo -e '---\na: 1234\nb: 4321' | ruby -ryaml -e "$RUBY_SCRIPT"
1234
4321

, wo dataist ein Hash (oder Array) mit den Werten von yaml.

Als Bonus wird Jekylls vordere Angelegenheit ganz gut analysiert .

ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md

1
ist es verwendbar? Sie haben Yaml per Echo an Ruby Interpreter gesendet. aber wie sollte diese Variable unter dem Rest des Bash-Skripts verwendet werden?
Znik

Ja, es ist verwendbar. Die RUBY_SCRIPTVariable ist ein Ruby-Skript, das stattdessen in eine Datei geschrieben werden kann (ausgeführt mit ruby -ryaml <rubyscript_filename>). Es enthält die Logik, den Eingabetext in einen Ausgabetext umzuwandeln und den Inhalt intern in der dataVariablen zu speichern . Das Echo gibt einen Yaml-Text aus, aber Sie können cat <yaml_filename>stattdessen den Inhalt einer Datei weiterleiten .
Rafael

Es tut mir leid, aber ich sehe das im obigen Beispiel nicht. Zunächst behält die Variable RUBY_SCRIPT den Code für den Ruby-Interpreter bei. Das nächste Echo -e simuliert alle Yaml-Daten. Dies erfolgt durch Stapel, der in einen Ruby-Interpreter umgeleitet wird. Dies ruft Ruby-Code als Inline-Skript auf und druckt schließlich, um Beispiele für die Variablen 'a' und 'b' auszugeben. Wo wird dann die Variable für seinen restlichen ausführbaren Code in bash geladen? Ich sehe nur eine Problemumgehung. Wenn Sie Ruby Outout in temporäre_Datei einfügen, sollte dies Zeilen enthalten: Variable = 'Wert', und danach laden Sie es in Bash by '. temporäre Datei'. Dies ist jedoch eine Problemumgehung, keine Lösung.
Znik

1
@Znik Sobald Sie etwas auf dem Standard haben, das von etwas produziert wird, das mit Standard gefüttert wurde, liegt der Rest in den Händen des Bash-Codierers (und zur Erinnerung, wenn Sie das benötigen stdout, um in eine Variable eingespeist zu werden, müssen Sie sich nicht darauf verlassen temporäre Dateien! verwenden x=$(...)oder sogar read a b c < <(...)). Dies ist also eine gültige Lösung, wenn Sie genau wissen, was Sie in der YAML-Datei abrufen möchten, und wissen, wie Sie die Rubinlinien schreiben, um auf diese Daten zuzugreifen. Auch wenn es rau ist, ist es meiner Meinung nach ein vollständiger Proof of Concept der Idee. Es ist jedoch wahr, dass es Ihnen keine vollständige Bash-Abstraktion bietet.
Vaab

Ja, so ist es. Du bist richtig. Danke für diesen Trick. Die Verwendung einer Variablen ist einfach. aber viele wariables sind nicht. Trick mit
Lesevariablenliste <<

23

Angesichts der Tatsache, dass Python3 und PyYAML heutzutage recht einfache Abhängigkeiten sind, kann Folgendes helfen:

yaml() {
    python3 -c "import yaml;print(yaml.safe_load(open('$1'))$2)"
}

VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")

Ich liebe Shyaml, aber auf getrennten Systemen ist dies ein Lebensretter. Sollte auch mit der überwiegenden Mehrheit von Python2 funktionieren, z. B. RHEL.
rsaw

2
Vielleicht verwenden, yaml.safe_loadda es sicherer ist. pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart

12

hier eine erweiterte Version der Antwort von Stefan Farestam:

function parse_yaml {
   local prefix=$2
   local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
   sed -ne "s|,$s\]$s\$|]|" \
        -e ":1;s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: [\3]\n\1  - \4|;t1" \
        -e "s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s\]|\1\2:\n\1  - \3|;p" $1 | \
   sed -ne "s|,$s}$s\$|}|" \
        -e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1  \3: \4|;t1" \
        -e    "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1  \2|;p" | \
   sed -ne "s|^\($s\):|\1|" \
        -e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p" \
        -e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|p" \
        -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" | \
   awk -F$fs '{
      indent = length($1)/2;
      vname[indent] = $2;
      for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}}
      if(length($2)== 0){  vname[indent]= ++idx[indent] };
      if (length($3) > 0) {
         vn=""; for (i=0; i<indent; i++) { vn=(vn)(vname[i])("_")}
         printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, vname[indent], $3);
      }
   }'
}

Diese Version unterstützt die -Notation und die Kurznotation für Wörterbücher und Listen. Die folgende Eingabe:

global:
  input:
    - "main.c"
    - "main.h"
  flags: [ "-O3", "-fpic" ]
  sample_input:
    -  { property1: value, property2: "value2" }
    -  { property1: "value3", property2: 'value 4' }

erzeugt diese Ausgabe:

global_input_1="main.c"
global_input_2="main.h"
global_flags_1="-O3"
global_flags_2="-fpic"
global_sample_input_1_property1="value"
global_sample_input_1_property2="value2"
global_sample_input_2_property1="value3"
global_sample_input_2_property2="value 4"

Wie Sie sehen können, werden die -Elemente automatisch nummeriert, um unterschiedliche Variablennamen für jedes Element zu erhalten. In bashes keine mehrdimensionale Arrays ist, so ist dies ein Weg , um zu arbeiten. Es werden mehrere Ebenen unterstützt. Um das von @briceburg erwähnte Problem mit nachgestellten Leerzeichen zu umgehen, sollten die Werte in einfache oder doppelte Anführungszeichen gesetzt werden. Es gibt jedoch noch einige Einschränkungen: Die Erweiterung der Wörterbücher und Listen kann zu falschen Ergebnissen führen, wenn Werte Kommas enthalten. Komplexere Strukturen wie Werte, die sich über mehrere Zeilen erstrecken (wie z. B. SSH-Schlüssel), werden (noch) nicht unterstützt.

Ein paar Worte zum Code: Der erste sedBefehl erweitert die Kurzform der Wörterbücher { key: value, ...}auf regulär und konvertiert sie in einen einfacheren Yaml-Stil. Der zweite sedAufruf macht dasselbe für die Kurznotation von Listen und konvertiert [ entry, ... ]in eine detaillierte Liste mit der -Notation. Der dritte sedAufruf ist der ursprüngliche Aufruf, der normale Wörterbücher handhabte, jetzt mit dem Zusatz, Listen mit -und Einrückungen zu behandeln. Der awkTeil führt einen Index für jede Einrückungsstufe ein und erhöht ihn, wenn der Variablenname leer ist (dh wenn eine Liste verarbeitet wird). Der aktuelle Wert der Zähler wird anstelle des leeren vnamens verwendet. Wenn Sie eine Ebene höher gehen, werden die Zähler auf Null gesetzt.

Bearbeiten: Ich habe dafür ein Github-Repository erstellt .


11

Schwer zu sagen, da es davon abhängt, was der Parser aus Ihrem YAML-Dokument extrahieren soll. Für einfache Fälle, könnten Sie in der Lage sein zu verwenden grep, cut, awkusw. Für komplexere Parsing Sie eine ausgewachsene verwenden Parsing - Bibliothek wie Python benötigen würde PyYAML oder YAML :: Perl .


11

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 keysArray mit einer Liste von Schlüsselnamen, ein childrenArray mit Namen von untergeordneten Arrays und einen parentSchlü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_parseFunktion sucht zuerst die inputDatei 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 bashBefehle 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 yayHelfer 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 awkdas 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 prefixfür das aktuelle Element verwendet werden soll. Dies wird einem Schlüsselnamen hinzugefügt, um einen Array-Namen zu erstellen. Es gibt ein root_prefixArray 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_keyist 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 prefixund 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 bashBefehle 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 prefixund 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 keysListe 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 childrenListe 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_parsekann von den Bash- evaloder sourceintegrierten Befehlen als Bash-Befehle analysiert werden.


Haben Sie darüber nachgedacht, dies zu einem Projekt auf GitHub zu machen? Oder ist es schon?
Daniel

@ Daniel, es ist in GitHub, aber nicht in einem eigenen Repo - Sie finden es hier . Siehe die examplesund usr/libVerzeichnisse, Diese sind in meiner Antwort auf die Frage verlinkt. Wenn es Interesse gibt, könnte ich es in ein eigenes Repo aufteilen.
Sternenhimmel

4
Ein großes Lob an YAY. Zuerst habe ich es umgeschrieben, um reine Bash zu sein, aber dann konnte ich mich nicht aufhalten und es als einfachen Parser mit Unterstützung für Arrays und verschachtelte Strukturen neu implementieren, die nicht auf die Namen der anderen treten können. Es ist bei github.com/binaryphile/y2s .
Binary Phile

5
perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh

Nützlich nur für flache Konfigurationen. es gilt nicht für strukturiertes yaml. eine andere, wie man die Verwendung von temporären file.sh verhindert?
Znik

5

Eine andere Möglichkeit besteht darin, die YAML in JSON zu konvertieren und dann mit jq mit der JSON-Darstellung zu interagieren, um entweder Informationen daraus zu extrahieren oder sie zu bearbeiten.

Ich habe ein einfaches Bash-Skript geschrieben, das diesen Kleber enthält - siehe Y2J-Projekt auf GitHub


2

Wenn Sie einen einzelnen Wert brauchen Sie ein Werkzeug , das Ihr YAML Dokument zu JSON konvertiert und Futtermittel jq, zum Beispiel yq.

Inhalt von sample.yaml:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples
  thing:
    cats: oranges

Beispiel:

$ yq -r '.bob["thing"]["cats"]' sample.yaml 
oranges

1

Ich weiß, dass dies sehr spezifisch ist, aber ich denke, meine Antwort könnte für bestimmte Benutzer hilfreich sein.
Wenn Sie nodeund npmauf Ihrem Computer installiert haben , können Sie verwenden js-yaml.
Erste Installation:

npm i -g js-yaml
# or locally
npm i js-yaml

dann in Ihrem Bash-Skript

#!/bin/bash
js-yaml your-yaml-file.yml

Auch wenn Sie verwenden jq, können Sie so etwas tun

#!/bin/bash
json="$(js-yaml your-yaml-file.yml)"
aproperty="$(jq '.apropery' <<< "$json")"
echo "$aproperty"

Weil js-yamleine yaml-Datei in ein json-String-Literal konvertiert. Sie können die Zeichenfolge dann mit jedem JSON-Parser in Ihrem Unix-System verwenden.


1

Wenn Sie Python 2 und PyYAML haben, können Sie diesen von mir geschriebenen Parser namens parse_yaml.py verwenden . Einige der übersichtlicheren Dinge sind, dass Sie ein Präfix auswählen können (falls Sie mehr als eine Datei mit ähnlichen Variablen haben) und einen einzelnen Wert aus einer Yaml-Datei auswählen.

Zum Beispiel, wenn Sie diese Yaml-Dateien haben:

staging.yaml:

db:
    type: sqllite
    host: 127.0.0.1
    user: dev
    password: password123

prod.yaml:

db:
    type: postgres
    host: 10.0.50.100
    user: postgres
    password: password123

Sie können beide ohne Konflikte laden.

$ eval $(python parse_yaml.py prod.yaml --prefix prod --cap)
$ eval $(python parse_yaml.py staging.yaml --prefix stg --cap)
$ echo $PROD_DB_HOST
10.0.50.100
$ echo $STG_DB_HOST
127.0.0.1

Und sogar Kirsche wählen Sie die Werte, die Sie wollen.

$ prod_user=$(python parse_yaml.py prod.yaml --get db_user)
$ prod_port=$(python parse_yaml.py prod.yaml --get db_port --default 5432)
$ echo prod_user
postgres
$ echo prod_port
5432

1

Sie könnten ein Äquivalent von yq verwenden , das in Golang geschrieben ist:

./go-yg -yamlFile /home/user/dev/ansible-firefox/defaults/main.yml -key
firefox_version

kehrt zurück:

62.0.3

0

Sie können auch Grunt (The JavaScript Task Runner) verwenden. Kann leicht in die Shell integriert werden. Es unterstützt das Lesen von YAML ( grunt.file.readYAML) - und JSON ( grunt.file.readJSON) -Dateien.

Dies kann erreicht werden, indem eine Aufgabe in Gruntfile.js(oder Gruntfile.coffee) erstellt wird, z.

module.exports = function (grunt) {

    grunt.registerTask('foo', ['load_yml']);

    grunt.registerTask('load_yml', function () {
        var data = grunt.file.readYAML('foo.yml');
        Object.keys(data).forEach(function (g) {
          // ... switch (g) { case 'my_key':
        });
    });

};

dann einfach von der Shell ausführen grunt foo( grunt --helpauf verfügbare Aufgaben prüfen ).

Darüber hinaus können Sie exec:footask ( grunt-exec) mit Eingabevariablen implementieren , die von Ihrer task ( foo: { cmd: 'echo bar <%= foo %>' }) übergeben wurden, um die Ausgabe in einem beliebigen Format zu drucken und dann in einen anderen Befehl weiterzuleiten.


Es gibt auch ein ähnliches Tool wie Grunt, es heißt gulp mit zusätzlichem Plugin gulp-yaml .

Installation über: npm install --save-dev gulp-yaml

Beispielnutzung:

var yaml = require('gulp-yaml');

gulp.src('./src/*.yml')
  .pipe(yaml())
  .pipe(gulp.dest('./dist/'))

gulp.src('./src/*.yml')
  .pipe(yaml({ space: 2 }))
  .pipe(gulp.dest('./dist/'))

gulp.src('./src/*.yml')
  .pipe(yaml({ safe: true }))
  .pipe(gulp.dest('./dist/'))

Weitere Optionen für das YAML-Format finden Sie auf der YAML-Website nach verfügbaren Projekten, Bibliotheken und anderen Ressourcen, mit denen Sie dieses Format analysieren können.


Andere Werkzeuge:

  • Jshon

    analysiert, liest und erstellt JSON


0

Ich weiß, dass meine Antwort spezifisch ist, aber wenn PHP und Symfony bereits installiert sind, kann es sehr praktisch sein, den YAML-Parser von Symfony zu verwenden.

Zum Beispiel:

php -r "require '$SYMFONY_ROOT_PATH/vendor/autoload.php'; \
    var_dump(\Symfony\Component\Yaml\Yaml::parse(file_get_contents('$YAML_FILE_PATH')));"

Hier habe ich einfach var_dumpdas analysierte Array ausgegeben, aber natürlich können Sie noch viel mehr ... :)

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.