Comment puis-je analyser un fichier YAML à partir d'un script shell Linux?


193

Je souhaite fournir un fichier de configuration structuré qui soit aussi facile que possible à modifier pour un utilisateur non technique (malheureusement, il doit s'agir d'un fichier) et j'ai donc voulu utiliser YAML. Cependant, je ne trouve aucun moyen d'analyser cela à partir d'un script shell Unix.


pas directement votre question, mais vous voudrez peut-être regarder ansible si votre écriture de shell concerne surtout la gestion à distance de différents nœuds (et un inventaire yaml)
eckes

9
Essayez d'utiliser yqpour lire / écrire des fichiers yaml dans le shell. La page du projet est ici: mikefarah.github.io/yq Vous pouvez installer l'outil avec brew, aptou télécharger le binaire. La lecture d'une valeur est aussi simple queyq r some.yaml key.value
vdimitrov

@kenorb JSON! = yml / YAML
swe

J'ai trouvé des fonctions étroitement liées au github de pkuczynski dont le meilleur (pour moi) était celui de jasperes, maintenu dans son propre github
splaisan

Réponses:


56

Mon cas d'utilisation peut ou non être tout à fait le même que ce que ce message original demandait, mais il est certainement similaire.

J'ai besoin de tirer quelques YAML en tant que variables bash. Le YAML n'aura jamais plus d'un niveau de profondeur.

YAML ressemble à ceci:

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

Sortie comme un dis:

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

J'ai réalisé le résultat avec cette ligne:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/gle trouve :et le remplace par =", tout en ignorant ://(pour les URL)
  • s/$/"/gajoute "à la fin de chaque ligne
  • s/ *=/=/g supprime tous les espaces avant =

13
Vous ne savez pas à quoi vous voulez en venir, mais si vous voulez dire que cela ne fonctionne pas pour tous les YAML, vous avez raison. C'est pourquoi j'ai ouvert avec quelques réserves. Je viens de partager ce qui a fonctionné pour mon cas d'utilisation, car il répondait à la question mieux que tout autre à l'époque. Cela peut certainement être étendu.
Curtis Blackwell

3
un peu ouvert à l'injection de code aussi, mais comme vous l'avez dit, c'est un pas en avant
Oriettaxx

1
Je n'ai jamais écrit que des scripts shell à utiliser localement, donc cela ne m'a pas préoccupé. Cependant, si vous savez comment le sécuriser et / ou souhaitez élaborer, je vous en serais certainement reconnaissant.
Curtis Blackwell

2
Le yaml à un niveau a de nombreuses formes - les valeurs peuvent être divisées en une ligne indentée suivante; les valeurs peuvent être citées de plusieurs manières que le shell n'analysera pas; tout peut être écrit sur une ligne avec des accolades: {KEY: 'value', ...}; et éventuellement d'autres. Plus important encore, si vous avez l'intention d'évaluer le résultat en tant que code shell, ce serait très peu sûr.
Beni Cherniavsky-Paskin

281

Voici un analyseur bash uniquement qui exploite sed et awk pour analyser des fichiers yaml simples:

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);
      }
   }'
}

Il comprend des fichiers tels que:

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

## output
output:
   file: "yes"

Qui, une fois analysé en utilisant:

parse_yaml sample.yml

affichera:

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

il comprend également les fichiers yaml, générés par ruby ​​qui peuvent inclure des symboles ruby, comme:

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

et affichera le même que dans l'exemple précédent.

l'utilisation typique dans un script est:

eval $(parse_yaml sample.yml)

parse_yaml accepte un argument de préfixe afin que les paramètres importés aient tous un préfixe commun (ce qui réduira le risque de collisions d'espace de noms).

parse_yaml sample.yml "CONF_"

donne:

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

Notez que les paramètres précédents d'un fichier peuvent être référencés par des paramètres ultérieurs:

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

## output
output:
   debug: $global_debug

Une autre utilisation intéressante consiste à analyser d'abord un fichier par défaut, puis les paramètres utilisateur, ce qui fonctionne car ces derniers remplacent les premiers:

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

3
Cool Stefan! Ce serait étonnant si cela pouvait aussi transformer la -notation yaml en tableaux bash natifs!
quickshift du

3
Cela devrait être assez facile à faire si vous modifiez la ligne printf dans le script awk. Notez cependant que bash ne prend pas en charge les tableaux associatifs multidimensionnels, vous vous retrouvez donc avec un tableau + une seule clé par valeur. Hmm, devrait probablement déplacer ceci vers github ...
Stefan Farestam

5
Cela attend l'indentation yml standard de 2 espaces. Si vous utilisez 4 espaces, les variables recevront deux traits de soulignement comme délimiteur, par exemple global__debugau lieu de global_debug.
k0pernikus

3
Salut vaab - Bien que je sois sûr que vous avez raison de dire que de nombreux lecteurs aimeraient analyser de vrais fichiers YAML à partir du shell, le résultat n'est pas tout à fait clair (du moins pour moi). Avec ce script, j'ai tenté de résoudre le problème et défini un sous-ensemble qui a un mappage raisonnable dans les variables standard. Il n'y a certainement aucune prétention d'avoir abordé le problème plus large de l'analyse des vrais fichiers YAML.
Stefan Farestam

3
Il imprime uniquement la sortie à l'écran. Comment accéderiez-vous aux valeurs plus tard?
sattu

96

J'ai écrit shyamlen python pour les besoins des requêtes YAML à partir de la ligne de commande du shell.

Aperçu:

$ pip install shyaml      ## installation

Exemple de fichier YAML (avec des fonctionnalités complexes):

$ 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

Requête de base:

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

Requête en boucle plus complexe sur des valeurs complexes:

$ 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'

Quelques points clés:

  • tous les types YAML et les bizarreries de syntaxe sont correctement gérés, sous forme de chaînes multilignes, entre guillemets, séquences en ligne ...
  • \0 la sortie rembourrée est disponible pour la manipulation d'entrée multiligne solide.
  • notation simple en pointillé pour sélectionner les sous-valeurs (c'est-à-dire: subvalue.maintainerest une clé valide).
  • l'accès par index est fourni aux séquences (c'est-à-dire: subvalue.things.-1est le dernier élément de la subvalue.thingsséquence.)
  • accès à tous les éléments de séquence / structs en une seule fois pour une utilisation dans les boucles bash.
  • vous pouvez générer une sous-partie entière d'un fichier YAML sous la forme ... YAML, qui se marient bien pour d'autres manipulations avec shyaml.

Plus d'exemples et de documentation sont disponibles sur la page shyaml github ou la page shyaml PyPI .


1
C'est génial! Ce serait génial s'il y avait un indicateur pour ignorer les valeurs yaml qui sont vides dans la sortie. À l'heure actuelle, il affiche "null". Je l'utilise avec envdir pour générer un fichier docker-compose vers envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket

@JiminyCricket Veuillez utiliser la page des problèmes de github! Je serais au moins heureux de garder une trace de cela. ;)
vaab

1
Malheureusement, shyamlest ridiculement lent
nowox

44

yq est un processeur YAML de ligne de commande léger et portable

Le but du projet est d'être le jq ou sed des fichiers yaml.

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

A titre d'exemple (volé directement dans la documentation ), étant donné un fichier sample.yaml de:

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

puis

yq r sample.yaml bob.*.cats

sortira

- bananas
- apples

il manque juste les capacités de filtrage
Antonin

formulae.brew.sh/formula/yq a été installé à 26 679 l'année dernière.
dustinevan

1
@Antonin Je ne sais pas si c'est ce que vous voulez dire, mais il semble qu'il ait maintenant des capacités de filtrage: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

Il est possible de passer un petit script à certains interprètes, comme Python. Un moyen simple de le faire en utilisant Ruby et sa bibliothèque YAML est le suivant:

$ 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

, où dataest un hachage (ou un tableau) avec les valeurs de yaml.

En prime, il analysera très bien les avant- propos de Jekyll .

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

1
est-il utilisable? vous avez mis yaml par écho à l'interpréteur ruby. mais comment utiliser cette variable sous le reste du script bash?
Znik

Oui, c'est utilisable. La RUBY_SCRIPTvariable est un script ruby ​​qui peut être écrit dans un fichier à la place (exécuté avec ruby -ryaml <rubyscript_filename>). Il contient la logique pour transformer le texte d'entrée en un texte de sortie, stockant en interne le contenu dans la datavariable. L'écho produit un texte yaml, mais vous pouvez l'utiliser cat <yaml_filename>pour diriger le contenu d'un fichier à la place.
Rafael

Je suis désolé mais je ne vois pas cela dans l'exemple ci-dessus. Au début, la variable RUBY_SCRIPT conserve le code pour l'interpréteur ruby. Ensuite, echo -e simule toutes les données yaml, c'est par pile redirigée vers l'interpréteur ruby. Cela appelle le code ruby ​​en tant que script en ligne et finalement imprimer pour afficher les exemples de variables «a» et «b». Alors, où la variable se charge-t-elle dans bash pour le reste du code exécutable? Je ne vois qu'une seule solution de contournement. mettre ruby ​​outout dans fichier_temporaire, qui devrait contenir des lignes: variable = 'value', et après cela, chargez-le dans bash par '. Fichier temporaire'. mais c'est une solution de contournement, pas une résolution.
Znik

1
@Znik une fois que vous avez quelque chose sur le stdout, produit par quelque chose alimenté avec stdin, le reste dépend des mains du codeur de bash (et pour rappel, si vous avez besoin que la stdoutvariable soit introduite dans la variable, vous n'avez pas à vous fier fichiers temporaires! utiliser x=$(...)ou même read a b c < <(...)). Il s'agit donc d'une solution valide lorsque vous savez exactement ce que vous voulez récupérer dans le fichier YAML et que vous savez comment écrire les lignes ruby ​​pour accéder à ces données. Même si c'est approximatif, c'est une preuve complète de concept de l'idée IMHO. Il est vrai néanmoins que cela ne vous fournit pas une abstraction complète.
vaab

Oui, ça l'est. Vous êtes bon. Merci pour cette astuce. Utiliser une variable est simple. mais beaucoup de wariables ne le sont pas. astuce avec la liste de variables de lecture <<(exécution vers stdout) est très utile :)
Znik

23

Étant donné que Python3 et PyYAML sont des dépendances assez faciles à rencontrer de nos jours, les éléments suivants peuvent aider:

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

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

J'adore shyaml, mais sur les systèmes déconnectés, c'est une bouée de sauvetage. Devrait également fonctionner avec la grande majorité de python2, par exemple RHEL.
rsaw

2
Peut-être utiliser yaml.safe_loadcar il est plus sûr. pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart

14

voici une version étendue de la réponse de 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);
      }
   }'
}

Cette version prend en charge la -notation et la notation courte pour les dictionnaires et les listes. L'entrée suivante:

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

produit cette sortie:

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"

comme vous pouvez le voir, les -éléments sont automatiquement numérotés afin d'obtenir des noms de variables différents pour chaque élément. Il bashn'y a pas de tableaux multidimensionnels, c'est donc une façon de contourner. Plusieurs niveaux sont pris en charge. Pour contourner le problème avec les espaces blancs de fin mentionnés par @briceburg, vous devez placer les valeurs entre guillemets simples ou doubles. Cependant, il existe encore certaines limitations: l'expansion des dictionnaires et des listes peut produire des résultats erronés lorsque les valeurs contiennent des virgules. De plus, les structures plus complexes comme les valeurs couvrant plusieurs lignes (comme les clés ssh) ne sont pas (encore) prises en charge.

Quelques mots sur le code: La première sedcommande étend la forme abrégée des dictionnaires { key: value, ...}en standard et les convertit en un style yaml plus simple. Le deuxième sedappel fait de même pour la notation courte des listes et se convertit [ entry, ... ]en une liste détaillée avec la -notation. Le troisième sedappel est celui d'origine qui traitait les dictionnaires normaux, maintenant avec l'ajout pour gérer les listes avec -et indentations. La awkpartie introduit un index pour chaque niveau d'indentation et l'augmente lorsque le nom de la variable est vide (c'est-à-dire lors du traitement d'une liste). La valeur actuelle des compteurs est utilisée à la place du vname vide. En remontant d'un niveau, les compteurs sont remis à zéro.

Edit: j'ai créé un référentiel github pour cela.


11

Difficile à dire car cela dépend de ce que vous voulez que l'analyseur extrait de votre document YAML. Pour les cas simples, vous pourriez être en mesure d'utiliser grep, cut, awketc. Pour l' analyse plus complexe , vous devez utiliser une bibliothèque entière analyse tels que Python PyYAML ou YAML :: Perl .


11

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 keystableau contenant une liste de noms de clés, un childrentableau contenant les noms des tableaux enfants et une parentclé 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_parsefonction localise d'abord le inputfichier 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 bashcommandes 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' yayaide:

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 awklequel 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 prefixfaut 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_prefixpour 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_keyest 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 prefixet 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 bashcommandes: 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 prefixet 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 keysliste 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 childrenliste 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_parsepeut être analysée en tant que commandes bash par les commandes bash evalou sourceintégrées.


Avez-vous envisagé d'en faire un projet sur GitHub? Ou est-ce déjà?
daniel

@daniel, il est dans GitHub mais pas dans son propre dépôt - vous pouvez le trouver ici . Voir les répertoires exampleset usr/lib, Ceux-ci sont liés dans ma réponse à la question. S'il y a un intérêt, je pourrais le diviser en son propre repo.
starfry

4
Félicitations à YAY. Au début, je l'ai réécrit pour être purement bash, mais ensuite je n'ai pas pu m'arrêter et je l'ai réimplémenté en tant qu'analyseur de base avec un support pour les tableaux et les structures imbriquées qui ne peuvent pas marcher sur les noms des autres. C'est sur github.com/binaryphile/y2s .
Binary Phile

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

utile uniquement pour les configurations plates. il ne s'applique pas au yaml structuré. un autre, comment éviter d'utiliser le fichier temporaire.sh?
Znik

5

Une autre option consiste à convertir le YAML en JSON, puis à utiliser jq pour interagir avec la représentation JSON, soit pour en extraire des informations, soit pour la modifier.

J'ai écrit un script bash simple qui contient cette colle - voir le projet Y2J sur GitHub


2

Si vous avez besoin d'une valeur unique, vous pouvez utiliser un outil qui convertit votre document YAML en JSON et alimente jq, par exemple yq.

Contenu de sample.yaml:

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

Exemple:

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

1

Je sais que c'est très précis, mais je pense que ma réponse pourrait être utile pour certains utilisateurs.
Si vous avez nodeet npminstallé sur votre machine, vous pouvez utiliser js-yaml.
Première installation:

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

puis dans votre script bash

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

Aussi, si vous utilisez, jqvous pouvez faire quelque chose comme ça

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

Parce que js-yamlconvertit un fichier yaml en un littéral de chaîne json. Vous pouvez ensuite utiliser la chaîne avec n'importe quel analyseur json de votre système unix.


1

Si vous avez python 2 et PyYAML, vous pouvez utiliser cet analyseur que j'ai écrit appelé parse_yaml.py . Certaines des choses les plus intéressantes qu'il fait est de vous permettre de choisir un préfixe (au cas où vous auriez plus d'un fichier avec des variables similaires) et de choisir une seule valeur dans un fichier yaml.

Par exemple, si vous avez ces fichiers yaml:

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

Vous pouvez charger les deux sans conflit.

$ 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

Et même cerise sur le gâteau, choisissez les valeurs que vous souhaitez.

$ 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

Vous pouvez utiliser un équivalent de yq écrit en golang:

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

Retour:

62.0.3

0

Vous pouvez également envisager d'utiliser Grunt (The JavaScript Task Runner). Peut être facilement intégré à la coque. Il prend en charge la lecture des fichiers YAML ( grunt.file.readYAML) et JSON ( grunt.file.readJSON).

Ceci peut être réalisé en créant une tâche dans Gruntfile.js(ou Gruntfile.coffee), par exemple:

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':
        });
    });

};

puis à partir du shell, exécutez simplement grunt foo(vérifiez grunt --helples tâches disponibles).

De plus, vous pouvez implémenter des exec:footâches ( grunt-exec) avec des variables d'entrée passées de votre tâche ( foo: { cmd: 'echo bar <%= foo %>' }) afin d'imprimer la sortie dans le format de votre choix, puis la diriger vers une autre commande.


Il existe également un outil similaire à Grunt, il s'appelle gulp avec le plugin supplémentaire gulp-yaml .

Installer via: npm install --save-dev gulp-yaml

Exemple d'utilisation:

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/'))

Pour plus d'options pour gérer le format YAML , consultez le site YAML pour les projets, bibliothèques et autres ressources disponibles qui peuvent vous aider à analyser ce format.


Autres outils:

  • Jshon

    analyse, lit et crée JSON


0

Je sais que ma réponse est spécifique, mais si PHP et Symfony sont déjà installés, il peut être très pratique d'utiliser l'analyseur YAML de Symfony.

Par exemple:

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

Ici, j'ai simplement utilisé var_dumppour sortir le tableau analysé mais bien sûr, vous pouvez faire beaucoup plus ... :)

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.