¿Cómo puedo analizar un archivo YAML desde un script de shell de Linux?


192

Deseo proporcionar un archivo de configuración estructurado que sea lo más fácil posible para que lo edite un usuario no técnico (desafortunadamente tiene que ser un archivo) y por eso quería usar YAML. Sin embargo, no puedo encontrar ninguna manera de analizar esto desde un script de shell de Unix.


no directamente a su pregunta, pero puede que desee mirar a ansible si su scriting cáscara es especialmente sobre cómo tratar con la administración remota de los diferentes nodos (y un inventario yaml)
Eckes

9
Intente usar yqpara leer / escribir archivos yaml en shell. La página del proyecto está aquí: mikefarah.github.io/yq Puede instalar la herramienta con brew, apto descargar el binario. Leer un valor es tan simple comoyq r some.yaml key.value
vdimitrov

@kenorb JSON! = yml / YAML
swe

Encontré funciones estrechamente relacionadas con el github de pkuczynski, el mejor (para mí) fue el de jasperes, mantenido en su propio github
splaisan

Respuestas:


56

Mi caso de uso puede o no ser el mismo que el que estaba preguntando esta publicación original, pero definitivamente es similar.

Necesito obtener algunos YAML como variables bash. El YAML nunca tendrá más de un nivel de profundidad.

YAML se ve así:

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

Salida like-a dis:

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

Logré el resultado con esta línea:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/glo encuentra :y lo reemplaza por =", mientras lo ignora ://(para URL)
  • s/$/"/gse agrega "al final de cada línea
  • s/ *=/=/g elimina todos los espacios antes =

13
No estoy seguro de a qué te refieres, pero si quieres decir que esto no funciona para todo YAML, tienes razón. Es por eso que abrí con algunas calificaciones. Acabo de compartir lo que funcionó para mi caso de uso, ya que respondió la pregunta mejor que cualquier otra en ese momento. Esto definitivamente se puede ampliar.
Curtis Blackwell

3
un poco abierto a la inyección de código también, pero como dijiste es un paso adelante
Oriettaxx

1
Solo he escrito scripts de shell para usar localmente, por lo que no ha sido una preocupación para mí. Sin embargo, si sabe cómo asegurarlo y / o desea elaborarlo, definitivamente lo agradecería.
Curtis Blackwell

2
Un nivel profundo yaml tiene muchas formas: los valores se pueden dividir en la siguiente línea sangrada; los valores se pueden citar de varias maneras, el shell no analizará; todo lo que se puede escribir en una línea con los apoyos: {KEY: 'value', ...}; y posiblemente otros. Lo más importante, si tiene la intención de evaluar el resultado como código shell, sería muy inseguro.
Beni Cherniavsky-Paskin

280

Aquí hay un analizador de solo bash que aprovecha sed y awk para analizar archivos 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);
      }
   }'
}

Entiende archivos como:

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

## output
output:
   file: "yes"

Que, cuando se analiza usando:

parse_yaml sample.yml

dará salida:

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

también comprende archivos yaml, generados por ruby ​​que pueden incluir símbolos ruby, como:

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

y generará lo mismo que en el ejemplo anterior.

El uso típico dentro de un script es:

eval $(parse_yaml sample.yml)

parse_yaml acepta un argumento de prefijo para que todas las configuraciones importadas tengan un prefijo común (lo que reducirá el riesgo de colisiones en el espacio de nombres).

parse_yaml sample.yml "CONF_"

rendimientos:

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

Tenga en cuenta que las configuraciones anteriores en un archivo pueden ser referidas por configuraciones posteriores:

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

## output
output:
   debug: $global_debug

Otro buen uso es analizar primero un archivo predeterminado y luego la configuración del usuario, que funciona ya que esta última anula la primera:

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

3
Genial Stefan! ¡Sería increíble si pudiera convertir la -notación yaml en matrices bash nativas también!
quickshiftin

3
Eso debería ser bastante fácil de hacer si cambia la línea printf en el script awk. Sin embargo, tenga en cuenta que bash no tiene soporte para matrices asociativas multidimensionales, por lo que termina con una matriz + una sola clave por valor. Hmm, probablemente debería mover esto a github ...
Stefan Farestam

55
Esto espera la sangría yml estándar de 2 espacios. Si está utilizando 4 espacios, las variables obtendrán dos guiones bajos como delimitador, por ejemplo, en global__debuglugar de global_debug.
k0pernikus

3
Hola vaab: aunque estoy seguro de que tienes razón en que a muchos lectores les gustaría analizar archivos YAML reales desde el shell, no está del todo claro (al menos para mí) cuál sería el resultado. Con este script, tomé una puñalada al problema y definí un subconjunto que tiene una asignación razonable en variables estándar. Ciertamente, no hay pretensión de haber abordado el problema más amplio de analizar archivos YAML reales.
Stefan Farestam

3
Solo imprime el resultado en la pantalla. ¿Cómo accedería a los valores más adelante?
sábado

96

He escrito shyamlen Python para las necesidades de consulta de YAML desde la línea de comandos del shell.

Visión general:

$ pip install shyaml      ## installation

Archivo YAML de ejemplo (con características complejas):

$ 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

Consulta básica:

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

Consulta de bucle más compleja sobre valores complejos:

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

Algunos puntos clave:

  • Todos los tipos YAML y las rarezas de sintaxis se manejan correctamente, como líneas múltiples, cadenas entre comillas, secuencias en línea ...
  • \0 La salida acolchada está disponible para la manipulación sólida de entradas multilínea.
  • notación punteada simple para seleccionar subvalores (es decir, subvalue.maintaineres una clave válida).
  • Se proporciona acceso por índice a las secuencias (es decir, subvalue.things.-1es el último elemento de la subvalue.thingssecuencia).
  • acceso a todos los elementos de secuencia / estructura de una sola vez para usar en bash loops.
  • puede generar una subparte completa de un archivo YAML como ... YAML, que se combina bien para manipulaciones posteriores con shyaml.

Hay más muestras y documentación disponibles en la página de Github de shyaml o en la página de PyPI de shyaml .


1
¡Esto es asombroso! Sería genial si hubiera una bandera para ignorar los valores de yaml que están en blanco en la salida. En este momento sale "nulo". Lo estoy usando junto con envdir para enviar un archivo docker-compose a envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket

@JiminyCricket ¡Utilice la página de problemas de github! Al menos me alegraría hacer un seguimiento de esto. ;)
vaab

1
Desafortunadamente, shyamles ridículamente lento
nowox

42

yq es un procesador YAML de línea de comandos ligero y portátil

El objetivo del proyecto es ser jq o sed de archivos yaml.

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

Como ejemplo (robado directamente de la documentación ), dado un archivo sample.yaml de:

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

luego

yq r sample.yaml bob.*.cats

saldrá

- bananas
- apples

simplemente carece de las capacidades de filtrado
Antonin

formulae.brew.sh/formula/yq tiene 26.679 instalar en el último año.
dustinevan

1
@Antonin No estoy seguro de si esto es lo que quieres decir, pero parece que ahora tiene algunas capacidades de filtrado: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

Es posible pasar un pequeño script a algunos intérpretes, como Python. Una manera fácil de hacerlo usando Ruby y su biblioteca YAML es la siguiente:

$ 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

, donde dataes un hash (o matriz) con los valores de yaml.

Como beneficio adicional, analizará muy bien el frente de Jekyll .

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

1
es usable? has puesto yaml por eco al intérprete de rubí. pero, ¿cómo se debe usar esta variable en el resto del script bash?
Znik

Si, es utilizable. La RUBY_SCRIPTvariable es un script ruby ​​que puede escribirse en un archivo en su lugar (ejecutarse con ruby -ryaml <rubyscript_filename>). Contiene la lógica para transformar el texto de entrada en texto de salida, almacenando internamente el contenido en la datavariable. El eco genera un texto yaml, pero puede utilizarlo cat <yaml_filename>para canalizar el contenido de un archivo.
Rafael

Lo siento, pero no veo esto en el ejemplo anterior. En la primera variable, RUBY_SCRIPT guarda el código para el intérprete ruby. El siguiente eco -e simula cualquier dato yaml, esto es por pila redirigida al intérprete ruby Esto llama al código ruby ​​como secuencia de comandos en línea y finalmente imprime para generar ejemplos de variables 'a' y 'b'. Entonces, ¿dónde está la carga variable en bash para su código ejecutable restante? Solo veo una solución alternativa. poner ruby ​​outout en temporary_file, eso debería contener líneas: variable = 'value', y luego cargarlo en bash por '. archivo temporal'. Pero esto es una solución, no una resolución.
Znik

1
@Znik una vez que tienes algo en el stdout, producido por algo alimentado con stdin, el resto depende de las manos del codificador bash (y como recordatorio, si necesitas stdoutque se alimente a la variable, no tienes que confiar en archivos temporales! uso x=$(...)o incluso read a b c < <(...)). Por lo tanto, esta es una solución válida cuando sabe exactamente lo que desea obtener en el archivo YAML y sabe cómo escribir las líneas rubí para acceder a estos datos. Incluso si es tosco, es una prueba completa del concepto de la idea en mi humilde opinión. Sin embargo, es cierto que no le proporciona una abstracción completa de bash.
vaab

Sí lo es. Eres rigt. Gracias por ese truco. Usar una variable es simple. pero muchos wariables no lo son. truco con la lista de variables de lectura <<(ejecución a stdout) es muy útil :)
Znik

23

Dado que Python3 y PyYAML son dependencias bastante fáciles de cumplir hoy en día, lo siguiente puede ayudar:

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

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

Me encanta shyaml, pero en sistemas desconectados es un salvavidas. También debería funcionar con la gran mayoría de python2, por ejemplo, RHEL.
rsaw

2
Tal vez usar yaml.safe_loadcomo es más seguro. pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart el

13

aquí una versión extendida de la respuesta 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);
      }
   }'
}

Esta versión admite la -notación y la notación corta para diccionarios y listas. La siguiente entrada:

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

produce esta salida:

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"

como puede ver, los -elementos se numeran automáticamente para obtener diferentes nombres de variables para cada elemento. En bashno hay matrices multidimensionales, por lo que esta es una forma de trabajo alrededor. Múltiples niveles son compatibles. Para evitar el problema de los espacios en blanco finales mencionados por @briceburg, se deben encerrar los valores entre comillas simples o dobles. Sin embargo, todavía hay algunas limitaciones: la expansión de los diccionarios y las listas puede producir resultados incorrectos cuando los valores contienen comas. Además, las estructuras más complejas, como los valores que abarcan varias líneas (como las teclas ssh) no son compatibles (todavía).

Algunas palabras sobre el código: el primer sedcomando expande la forma abreviada de los diccionarios { key: value, ...}a regular y los convierte a un estilo yaml más simple. La segunda sedllamada hace lo mismo para la notación corta de listas y se convierte [ entry, ... ]en una lista detallada con la -notación. La tercera sedllamada es la original que maneja los diccionarios normales, ahora con la adición de manejar listas con -sangrías. La awkparte introduce un índice para cada nivel de sangría y lo aumenta cuando el nombre de la variable está vacío (es decir, al procesar una lista). Se utiliza el valor actual de los contadores en lugar del nombre de v vacío. Al subir un nivel, los contadores se ponen a cero.

Editar: he creado un repositorio github para esto.


11

Difícil de decir porque depende de lo que quiera que extraiga el analizador de su documento YAML. Para los casos simples, es posible que pueda utilizar grep, cut, awketc. Para el análisis más complejo que tendría que utilizar un análisis en toda regla biblioteca de Python como PyYAML o YAML :: Perl .


11

¡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 keysmatriz que contiene una lista de nombres de clave, una childrenmatriz que contiene nombres de matrices secundarias y una parentclave 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_parsefunción primero localiza el inputarchivo 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 bashcomandos 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 yayayudante:

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 awkque 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é prefixusar para el elemento actual. Esto es lo que se agrega al nombre de una clave para crear un nombre de matriz. Hay una root_prefixpara 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_keyes 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 prefixy 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 bashcomandos: 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 prefixy 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 keyslista 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 childrenlista 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_parsepuede ser analizada como comandos bash por los comandos bash evalo sourceintegrados.


¿Has considerado hacer de este un proyecto en GitHub? ¿O ya lo es?
daniel

@daniel, está en GitHub pero no en su propio repositorio, puedes encontrarlo aquí . Consulte los directorios examplesy usr/lib, Estos están vinculados en mi respuesta a la pregunta. Si hay interés, podría dividirlo en su propio repositorio.
starfry

44
Felicitaciones en YAY. Al principio, lo reescribí para que fuera puro bash, pero luego no pude detenerme y lo reimplementé como un analizador básico con soporte para matrices y estructuras anidadas que no pueden pisar los nombres de los demás. Está en github.com/binaryphile/y2s .
Binary Phile

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

Útil solo para configuraciones planas. No es aplicable para YAML estructurado. otro, ¿cómo evitar el uso de file.sh temporal?
Znik

5

Otra opción es convertir el YAML a JSON, luego usar jq para interactuar con la representación JSON, ya sea para extraer información o editarla.

Escribí un script de bash simple que contiene este pegamento; vea el proyecto Y2J en GitHub


2

Si necesita un valor único, podría utilizar una herramienta que convierta su documento YAML a JSON y lo alimente jq, por ejemplo yq.

Contenido de sample.yaml:

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

Ejemplo:

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

1

Sé que esto es muy específico, pero creo que mi respuesta podría ser útil para ciertos usuarios.
Si tiene nodee npminstaló en su máquina, puede usar js-yaml.
Primera instalación:

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

entonces en tu script bash

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

Además, si estás usando jqpuedes hacer algo así

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

Porque js-yamlconvierte un archivo yaml en un literal de cadena json. Luego puede usar la cadena con cualquier analizador json en su sistema unix.


1

Si tiene Python 2 y PyYAML, puede usar este analizador que escribí llamado parse_yaml.py . Algunas de las cosas más ingeniosas que hace es permitirle elegir un prefijo (en caso de que tenga más de un archivo con variables similares) y elegir un solo valor de un archivo yaml.

Por ejemplo, si tiene estos archivos 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

Puede cargar ambos sin conflicto.

$ 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

E incluso elige los valores que quieras.

$ 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

Podrías usar un equivalente de yq que está escrito en golang:

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

devoluciones:

62.0.3

0

También puede considerar usar Grunt (The JavaScript Task Runner). Se puede integrar fácilmente con la carcasa. Admite la lectura de archivos YAML ( grunt.file.readYAML) y JSON ( grunt.file.readJSON).

Esto se puede lograr creando una tarea en Gruntfile.js(o Gruntfile.coffee), por ejemplo:

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

};

luego desde shell simplemente ejecute grunt foo(verifique grunt --helplas tareas disponibles)

Además, puede implementar exec:footareas ( grunt-exec) con variables de entrada pasadas desde su tarea ( foo: { cmd: 'echo bar <%= foo %>' }) para imprimir la salida en el formato que desee y luego canalizarla a otro comando.


También hay una herramienta similar a Grunt, se llama gulp con el complemento adicional gulp-yaml .

Instalar a través de: npm install --save-dev gulp-yaml

Uso de la muestra:

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

Para obtener más opciones para lidiar con el formato YAML , consulte el sitio de YAML para ver los proyectos, bibliotecas y otros recursos disponibles que pueden ayudarlo a analizar ese formato.


Otras herramientas:

  • Jshon

    analiza, lee y crea JSON


0

Sé que mi respuesta es específica, pero si uno ya tiene PHP y Symfony instalados, puede ser muy útil usar el analizador YAML de Symfony.

Por ejemplo:

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

Aquí simplemente solía var_dumpgenerar la matriz analizada pero, por supuesto, puedes hacer mucho más ... :)

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.