Come posso analizzare un file YAML da uno script di shell Linux?


193

Vorrei fornire un file di configurazione strutturato che sia il più semplice possibile per un utente non tecnico da modificare (purtroppo deve essere un file) e quindi volevo usare YAML. Tuttavia, non riesco a trovare alcun modo per analizzare questo da uno script della shell Unix.


non direttamente alla tua domanda, ma potresti voler dare una risposta se lo scriting della shell riguarda soprattutto la gestione remota di diversi nodi (e un inventario yaml)
eckes

9
Prova a usare yqper leggere / scrivere file yaml nella shell. La pagina del progetto è qui: mikefarah.github.io/yq È possibile installare lo strumento con brew, apto scaricare il binario. Leggere un valore è semplice comeyq r some.yaml key.value
vdimitrov l'

@kenorb JSON! = yml / YAML
swe

Ho trovato funzioni strettamente correlate di github di pkuczynski di cui la migliore (per me) era quella di Jasperes, mantenuta nel suo github
splaisan il

Risposte:


56

Il mio caso d'uso può essere o meno lo stesso di quello che chiedeva questo post originale, ma è decisamente simile.

Ho bisogno di inserire alcuni YAML come variabili bash. Lo YAML non sarà mai profondo più di un livello.

YAML si presenta così:

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

Output like-a dis:

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

Ho raggiunto l'output con questa linea:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/gtrova :e lo sostituisce con =", ignorando ://(per URL)
  • s/$/"/gaggiunge "alla fine di ogni riga
  • s/ *=/=/g rimuove tutti gli spazi prima =

13
Non sei sicuro di cosa stai arrivando, ma se intendi che non funziona per tutti gli YAML, hai ragione. Ecco perché ho aperto con alcune qualifiche. Ho appena condiviso ciò che ha funzionato per il mio caso d'uso, dal momento che ha risposto alla domanda meglio di qualsiasi altro al momento. Questo può sicuramente essere ampliato.
Curtis Blackwell,

3
un po 'aperto anche all'iniezione di codice, ma come hai detto è un passo avanti
Oriettaxx,

1
Ho sempre scritto script di shell da usare localmente, quindi non è stato un problema per me. Tuttavia, se sai come proteggerlo e / o vorresti elaborarlo, sarei sicuramente grato.
Curtis Blackwell,

2
Lo yaml profondo a un livello ha molte forme: i valori possono essere divisi sulla seguente linea rientrata; i valori possono essere citati in diversi modi in cui la shell non analizzerà; tutto può essere scritto su una riga con bretelle: {KEY: 'value', ...}; e forse altri. Soprattutto, se si intende valutare il risultato come codice shell, sarebbe molto insicuro.
Beni Cherniavsky-Paskin,

281

Ecco un parser solo bash che sfrutta sed e awk per analizzare semplici file yaml:

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

Comprende file come:

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

## output
output:
   file: "yes"

Che, se analizzato usando:

parse_yaml sample.yml

produrrà:

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

comprende anche i file yaml, generati da ruby ​​che possono includere simboli ruby, come:

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

e produrrà lo stesso dell'esempio precedente.

l'uso tipico all'interno di uno script è:

eval $(parse_yaml sample.yml)

parse_yaml accetta un argomento prefisso in modo che tutte le impostazioni importate abbiano un prefisso comune (che ridurrà il rischio di collisioni dello spazio dei nomi).

parse_yaml sample.yml "CONF_"

rendimenti:

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

Si noti che le impostazioni precedenti in un file possono essere indicate da impostazioni successive:

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

## output
output:
   debug: $global_debug

Un altro uso utile è prima di analizzare un file predefinito e quindi le impostazioni utente, che funzionano poiché queste ultime hanno la precedenza su quelle precedenti:

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

3
Cool Stefan! Sarebbe fantastico se potesse trasformare anche la -notazione yaml in array bash nativi!
cambio rapido

3
Dovrebbe essere abbastanza facile da fare se si modifica la riga printf nello script awk. Nota però che bash non ha supporto per array associativi multidimensionali, quindi finisci con un array + una sola chiave per valore. Hmm, probabilmente dovrei spostarlo su github ...
Stefan Farestam,

5
Ciò prevede l'indentazione yml standard di 2 spazi. Se si utilizzano 4 spazi, le variabili otterranno due trattini bassi come delimitatore, ad es. global__debugAnziché global_debug.
k0pernikus,

3
Ciao vaab - Anche se sono sicuro che hai ragione sul fatto che molti lettori vorrebbero analizzare i file YAML reali dalla shell, non è del tutto chiaro (almeno per me) quale sarebbe il risultato. Con questo script ho preso una decisione sul problema e ho definito un sottoinsieme che ha una mappatura ragionevole in variabili standard. Non si può certo pretendere di aver affrontato il problema più ampio dell'analisi dei file YAML reali.
Stefan Farestam,

3
Stampa solo l'output sullo schermo. Come accederesti ai valori in un secondo momento?
sabato

96

Ho scritto shyamlin Python per le esigenze di query YAML dalla riga di comando della shell.

Panoramica:

$ pip install shyaml      ## installation

File YAML di esempio (con funzionalità complesse):

$ 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

Query di base:

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

Query di loop più complessa su valori complessi:

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

Alcuni punti chiave:

  • tutti i tipi di YAML e le stranezze della sintassi sono gestiti correttamente, come multilinea, stringhe tra virgolette, sequenze incorporate ...
  • \0 l'output imbottito è disponibile per una solida manipolazione dell'entrata multilinea.
  • semplice notazione punteggiata per selezionare sotto-valori (es .: subvalue.maintainerè una chiave valida).
  • l'accesso per indice è fornito alle sequenze (cioè: subvalue.things.-1è l'ultimo elemento della subvalue.thingssequenza.)
  • accesso a tutti gli elementi di sequenza / struttura in una volta sola per l'uso in loop bash.
  • puoi generare l'intero sottoparte di un file YAML come ... YAML, che si fonde bene per ulteriori manipolazioni con shyaml.

Ulteriori esempi e documentazione sono disponibili sulla pagina Github di shyaml o sulla pagina PyPI di shyaml .


1
Questo e spettacolare! Sarebbe bello se ci fosse un flag per ignorare i valori yaml che sono vuoti nell'output. In questo momento restituisce "null". Lo sto usando insieme a envdir per produrre un file comporre docker su envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
JiminyCricket,

@JiminyCricket Ti preghiamo di utilizzare la pagina dei problemi di github! Sarei felice almeno di tenerne traccia. ;)
vaab,

1
Sfortunatamente, shyamlè ridicolmente lento
ora il

44

YQ è un processore YAML da riga di comando leggero e portatile

Lo scopo del progetto è di essere il jq o sed dei file yaml.

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

Ad esempio (rubato direttamente dalla documentazione ), dato un file sample.yaml di:

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

poi

yq r sample.yaml bob.*.cats

uscirà

- bananas
- apples

manca solo la capacità di filtraggio
Antonin,

formulae.brew.sh/formula/yq ha 26.679 installazioni nell'ultimo anno.
dustinevan,

1
@Antonin Non sono sicuro se questo è ciò che vuoi dire, ma sembra che abbia alcune funzionalità di filtro ora: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

È possibile passare una piccola sceneggiatura ad alcuni interpreti, come Python. Un modo semplice per farlo utilizzando Ruby e la sua libreria YAML è il seguente:

$ 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

, dove si datatrova un hash (o matrice) con i valori di yaml.

Come bonus, analizzerà bene la questione di Jekyll .

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

1
è utilizzabile? hai messo yaml per eco all'interprete rubino. ma come si dovrebbe usare questa variabile sotto il resto dello script bash?
Znik,

Sì, è utilizzabile. La RUBY_SCRIPTvariabile è uno script ruby ​​che può invece essere scritto in un file (eseguito con ruby -ryaml <rubyscript_filename>). Contiene la logica per trasformare il testo di input in un testo di output, memorizzando internamente il contenuto nella datavariabile. L'eco genera un testo yaml, ma è possibile utilizzare invece cat <yaml_filename>per reindirizzare il contenuto di un file.
Rafael,

Mi dispiace ma non vedo questo nell'esempio sopra. Alla prima variabile RUBY_SCRIPT mantiene il codice per l'interprete ruby. Il prossimo eco -e simula qualsiasi dato yaml, questo è da pile reindirizzate all'interprete rubino. Questo chiama il codice ruby ​​come script inline e infine stampa sull'output degli esempi 'a' e 'b' variabili. Quindi dove si trova il caricamento variabile in bash per il suo resto codice eseguibile? Vedo solo una soluzione alternativa. inserendo il ritaglio del rubino in un file temporaneo, che dovrebbe contenere le righe: variabile = 'valore', e successivamente caricarlo in bash con '. temporary_file'. ma questa è una soluzione alternativa, non una risoluzione.
Znik,

1
@Znik una volta che hai qualcosa sullo stdout, prodotto da qualcosa alimentato con stdin, il resto si affida alle mani del programmatore bash (e come promemoria, se hai bisogno stdoutdi essere inserito nella variabile, non devi fare affidamento su file temporanei! usa x=$(...)o anche read a b c < <(...)). Quindi, questa è una soluzione valida quando sai esattamente cosa vuoi recuperare nel file YAML e sai come scrivere le linee ruby ​​per accedere a questi dati. Anche se è approssimativo, è una prova completa del concetto dell'idea IMHO. È vero comunque che non ti fornisce un'astrazione bash completa.
vaab,

Sì. Sei rigido. Grazie per quel trucco. L'uso di una variabile è semplice. ma molti wariable non lo sono. il trucco con l'elenco delle variabili di lettura <<(esecuzione su stdout) è molto utile :)
Znik

23

Dato che Python3 e PyYAML sono dipendenze abbastanza facili da soddisfare al giorno d'oggi, potrebbero essere utili:

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

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

Adoro shyaml, ma su sistemi disconnessi questo è un salvavita. Dovrebbe funzionare anche con la stragrande maggioranza di python2, ad esempio RHEL.
visto il

2
Forse usare yaml.safe_loadin quanto è più sicuro. pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart

14

qui una versione estesa della risposta di 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);
      }
   }'
}

Questa versione supporta la -notazione e la breve notazione per dizionari ed elenchi. Il seguente input:

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

produce questo output:

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"

come puoi vedere, gli -articoli vengono automaticamente numerati per ottenere nomi di variabili differenti per ogni articolo. Nelbash ci sono matrici multidimensionali, quindi questo è un modo per aggirare. Sono supportati più livelli. Per aggirare il problema con gli spazi bianchi finali menzionati da @briceburg, è necessario racchiudere i valori tra virgolette singole o doppie. Tuttavia, ci sono ancora alcune limitazioni: l'espansione dei dizionari e degli elenchi può produrre risultati errati quando i valori contengono virgole. Inoltre, strutture più complesse come i valori che si estendono su più righe (come ssh-keys) non sono (ancora) supportate.

Alcune parole sul codice: il primo sedcomando espande la forma abbreviata di dizionari { key: value, ...}in normali e li converte in uno stile yaml più semplice. La seconda sedchiamata fa lo stesso per la breve notazione di elenchi e si converte [ entry, ... ]in un elenco dettagliato con la -notazione. La terza sedchiamata è quella originale che ha gestito dizionari normali, ora con l'aggiunta di gestire elenchi con -e rientri. La awkparte introduce un indice per ogni livello di rientro e lo aumenta quando il nome della variabile è vuoto (cioè durante l'elaborazione di un elenco). Viene utilizzato il valore corrente dei contatori anziché il vname vuoto. Quando si sale di un livello, i contatori vengono azzerati.

Modifica: ho creato un repository github per questo.


11

Difficile a dirsi perché dipende da cosa vuoi che il parser estragga dal tuo documento YAML. Per i casi semplici, si potrebbe essere in grado di utilizzare grep, cut, awkecc Per l'analisi più complessa si avrebbe bisogno di utilizzare una vera e parsing biblioteca come di Python PyYAML o YAML :: Perl .


11

Ho appena scritto un parser che ho chiamato Yay! ( Yaml non è Yamlesque! ) Che analizza Yamlesque , un piccolo sottoinsieme di YAML. Quindi, se stai cercando un parser YAML conforme al 100% per Bash, non è questo. Tuttavia, per citare l'OP, se si desidera modificare un file di configurazione strutturato il più semplice possibile per un utente non tecnico simile a YAML, ciò può essere interessante.

È ispirato dalla risposta precedente ma scrive array associativi ( sì, richiede Bash 4.x ) invece di variabili di base. Lo fa in un modo che consente di analizzare i dati senza una conoscenza preliminare delle chiavi in ​​modo da poter scrivere il codice basato sui dati.

Oltre agli elementi array chiave / valore, ogni array ha un keysarray contenente un elenco di nomi chiave, un childrenarray contenente nomi di array figlio e una parentchiave che fa riferimento al suo genitore.

Questo è un esempio di 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

Ecco un esempio che mostra come 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

che produce:

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
  }
}

Ed ecco il 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 "$@"); }

C'è della documentazione nel file sorgente collegato e di seguito è una breve spiegazione di ciò che fa il codice.

La yay_parsefunzione individua innanzitutto il inputfile o esce con uno stato di uscita di 1. Successivamente, determina il set di dati prefix, esplicitamente specificato o derivato dal nome del file.

Scrive bashcomandi validi nel suo output standard che, se eseguiti, definiscono array che rappresentano il contenuto del file di dati di input. Il primo di questi definisce l'array di primo livello:

echo "declare -g -A $prefix;"

Si noti che le dichiarazioni di array sono associative ( -A) che è una caratteristica di Bash versione 4. Le dichiarazioni sono anche globali ( -g), quindi possono essere eseguite in una funzione ma essere disponibili nell'ambito globale come l' yayhelper:

yay() { eval $(yay_parse "$@"); }

I dati di input vengono inizialmente elaborati con sed. Elimina le righe che non corrispondono alle specifiche del formato Yamlesque prima di delimitare i campi Yamlesque validi con un carattere di separatore di file ASCII e rimuovere le virgolette doppie che circondano il campo valore.

 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" |

Le due espressioni sono simili; differiscono solo perché il primo seleziona i valori tra virgolette dove il secondo seleziona quelli non quotati.

Il separatore di file (28 / hex 12 / ottale 034) viene utilizzato perché, come carattere non stampabile, è improbabile che si trovi nei dati di input.

Il risultato viene convogliato in awkcui elabora il suo input una riga alla volta. Utilizza il carattere FS per assegnare ciascun campo a una variabile:

indent       = length($1)/2;
key          = $2;
value        = $3;

Tutte le righe hanno un rientro (possibilmente zero) e una chiave ma non tutte hanno un valore. Calcola un livello di rientro per la linea che divide la lunghezza del primo campo, che contiene lo spazio bianco iniziale, per due. Gli elementi di livello superiore senza alcun rientro sono al livello di rientro zero.

Successivamente, determina cosa prefixutilizzare per l'elemento corrente. Questo è ciò che viene aggiunto al nome di una chiave per creare un nome di array. Esiste un root_prefixarray di livello superiore che è definito come il nome del set di dati e un trattino basso:

root_prefix  = "'$prefix'_";
if (indent ==0 ) {
  prefix = "";          parent_key = "'$prefix'";
} else {
  prefix = root_prefix; parent_key = keys[indent-1];
}

La parent_keyè la chiave al livello di rientro sopra livello di rientro della riga corrente e rappresenta la raccolta che la linea corrente appartiene. Le coppie chiave / valore della raccolta verranno archiviate in un array con il nome definito come concatenazione di prefixe parent_key.

Per il livello superiore (rientro livello zero) il prefisso del set di dati viene utilizzato come chiave principale, quindi non ha prefisso (è impostato su ""). Tutti gli altri array hanno il prefisso root.

Successivamente, la chiave corrente viene inserita in un array (interno-awk) contenente le chiavi. Questo array persiste per tutta la sessione di awk e quindi contiene chiavi inserite da righe precedenti. La chiave viene inserita nell'array usando il suo rientro come indice dell'array.

keys[indent] = key;

Poiché questo array contiene chiavi delle righe precedenti, tutte le chiavi con una grattugia di livello di rientro rispetto al livello di rientro della riga corrente vengono rimosse:

 for (i in keys) {if (i > indent) {delete keys[i]}}

Ciò lascia l'array di chiavi contenente la catena chiave dalla radice al livello di rientro 0 alla riga corrente. Rimuove le chiavi obsolete che rimangono quando la riga precedente è stata rientrata più in profondità rispetto alla riga corrente.

La sezione finale genera i bashcomandi: una riga di input senza un valore avvia un nuovo livello di rientro (una raccolta in linguaggio YAML) e una riga di input con un valore aggiunge una chiave alla raccolta corrente.

Il nome della collezione è la concatenazione della linea corrente prefixe parent_key.

Quando una chiave ha un valore, una chiave con quel valore viene assegnata alla raccolta corrente in questo modo:

printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);

La prima istruzione genera il comando per assegnare il valore a un elemento di array associativo che prende il nome dalla chiave e la seconda genera il comando per aggiungere la chiave keysall'elenco delimitato da spazi della raccolta :

<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";

Quando una chiave non ha un valore, viene avviata una nuova raccolta in questo modo:

printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);

La prima istruzione genera il comando per aggiungere la nuova raccolta childrenall'elenco delimitato da spazi della raccolta corrente e la seconda emette il comando per dichiarare una nuova matrice associativa per la nuova raccolta:

<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;

Tutto l'output di yay_parsepuò essere analizzato come comandi bash dai comandi bash evalo sourceintegrati.


Hai mai pensato di trasformare questo in un progetto su GitHub? O lo è già?
Daniel

@daniel, è in GitHub ma non nel suo repository - puoi trovarlo qui . Vedi le directory examplese usr/lib, Queste sono collegate nella mia risposta alla domanda. Se c'è interesse, potrei inserirlo nel suo repository.
Starfry,

4
Complimenti per YAY. All'inizio, l'ho riscritto per essere puro bash, ma poi non sono riuscito a fermarmi e lo ho reimplementato come un parser di base con supporto per array e strutture nidificate che non possono calpestare i nomi degli altri. È su github.com/binaryphile/y2s .
File binario

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

utile solo per configurazioni piatte. non è applicabile per yaml strutturato. un altro, come prevenire l'utilizzo di file.sh temporaneo?
Znik,

5

Un'altra opzione è quella di convertire YAML in JSON, quindi utilizzare jq per interagire con la rappresentazione JSON per estrarre informazioni da esso o modificarlo.

Ho scritto un semplice script bash che contiene questa colla - vedi il progetto Y2J su GitHub


2

Se hai bisogno di un singolo valore, potresti utilizzare uno strumento che converte il tuo documento YAML in JSON e il feed jq, ad esempio yq.

Contenuto di sample.yaml:

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

Esempio:

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

1

So che questo è molto specifico, ma penso che la mia risposta possa essere utile per alcuni utenti.
Se l'hai installato nodee npminstallato sul tuo computer, puoi usarlo js-yaml.
Prima installazione:

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

quindi nel tuo script bash

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

Anche se stai usando jqpuoi fare qualcosa del genere

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

Perché js-yamlconverte un file yaml in una stringa json letterale. È quindi possibile utilizzare la stringa con qualsiasi parser json nel sistema unix.


1

Se hai Python 2 e PyYAML, puoi usare questo parser che ho scritto chiamato parse_yaml.py . Alcune delle cose più pulite che fa è quella di scegliere un prefisso (nel caso in cui tu abbia più di un file con variabili simili) e di scegliere un singolo valore da un file yaml.

Ad esempio se hai questi file 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

Puoi caricare entrambi senza conflitti.

$ 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 anche ciliegia scegli i valori che desideri.

$ 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

Puoi usare un equivalente di yq scritto in golang:

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

ritorna:

62.0.3

0

Puoi anche prendere in considerazione l'uso di Grunt (il JavaScript Runner). Può essere facilmente integrato con la shell. Supporta la lettura di YAML ( grunt.file.readYAML) e JSON (grunt.file.readJSON ).

Ciò può essere ottenuto creando un'attività in Gruntfile.js(o Gruntfile.coffee), ad esempio:

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

};

quindi dalla shell semplicemente esegui grunt foo(controlla grunt --helple attività disponibili).

Inoltre puoi implementare exec:footask ( grunt-exec) con variabili di input passate dal tuo task ( foo: { cmd: 'echo bar <%= foo %>' }) al fine di stampare l'output nel formato che preferisci, quindi inserirlo in un altro comando.


C'è anche uno strumento simile a Grunt, si chiama gulp con plug -in aggiuntivo gulp-yaml .

Installa tramite: npm install --save-dev gulp-yaml

Esempio di utilizzo:

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

Per ulteriori opzioni per gestire il formato YAML , controlla il sito YAML per i progetti disponibili, le librerie e altre risorse che possono aiutarti ad analizzare quel formato.


Altri strumenti:

  • Jshon

    analizza, legge e crea JSON


0

So che la mia risposta è specifica, ma se uno ha già installato PHP e Symfony , può essere molto utile usare il parser YAML di Symfony.

Per esempio:

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

Qui ho usato semplicemente var_dumpl'output della matrice analizzata ma ovviamente puoi fare molto di più ... :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.