Como posso analisar um arquivo YAML a partir de um script de shell do Linux?


192

Desejo fornecer um arquivo de configuração estruturado que seja o mais fácil possível para um usuário não técnico editar (infelizmente deve ser um arquivo) e, portanto, desejei usar o YAML. No entanto, não consigo encontrar nenhuma maneira de analisar isso em um script de shell Unix.


não diretamente a sua pergunta, mas você pode querer olhar para ansible se o seu scriting shell é especialmente sobre como lidar com o gerenciamento remoto de diferentes nós (e um inventário yaml)
Eckes

9
Tente usar yqpara ler / gravar arquivos yaml no shell. A página do projeto está aqui: mikefarah.github.io/yq Você pode instalar a ferramenta com brew, aptou fazer o download do binário. Ler um valor é tão simples quantoyq r some.yaml key.value
vdimitrov

@kenorb JSON! = yml / YAML
swe

Achei de perto as funções relacionadas github das pkuczynski de que o melhor (para mim) foi que a partir jasperes de, mantida em sua própria github
splaisan

Respostas:


56

Meu caso de uso pode ou não ser exatamente o que esta postagem original estava perguntando, mas é definitivamente semelhante.

Eu preciso puxar alguns YAML como variáveis ​​bash. O YAML nunca terá mais de um nível de profundidade.

YAML é assim:

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

Saída como um dis:

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

Eu consegui a saída com esta linha:

sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
  • s/:[^:\/\/]/="/glocaliza :e o substitui por =", enquanto ignora ://(para URLs)
  • s/$/"/ganexa "ao final de cada linha
  • s/ *=/=/g remove todos os espaços antes =

13
Não tenho certeza do que você está falando, mas se você quer dizer que isso não funciona para todos os YAML, você está certo. Por isso abri com algumas qualificações. Acabei de compartilhar o que funcionou no meu caso de uso, pois ele respondeu à pergunta melhor do que qualquer outro na época. Definitivamente, isso pode ser expandido.
Curtis Blackwell

3
um pouco aberto a injeção de código também, mas como você disse é um passo em frente
Oriettaxx

1
Eu só escrevi scripts de shell para usar localmente, então isso não foi uma preocupação para mim. No entanto, se você souber como protegê-lo e / ou gostaria de elaborar, eu ficaria muito grato.
Curtis Blackwell

2
O yaml com um nível de profundidade tem muitas formas - os valores podem ser divididos para seguir a linha recuada; os valores podem ser citados de várias maneiras que o shell não analisará; tudo pode ser escrito em uma linha com as cintas: {KEY: 'value', ...}; e possivelmente outros. Mais importante, se você pretende avaliar o resultado como código de shell, isso seria muito inseguro.
Home

280

Aqui está um analisador bash-only que aproveita o sed e o awk para analisar arquivos 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);
      }
   }'
}

Ele entende arquivos como:

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

## output
output:
   file: "yes"

Que, quando analisado usando:

parse_yaml sample.yml

irá produzir:

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

ele também entende arquivos yaml, gerados por ruby, que podem incluir símbolos ruby, como:

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

e produzirá o mesmo que no exemplo anterior.

O uso típico em um script é:

eval $(parse_yaml sample.yml)

parse_yaml aceita um argumento de prefixo para que todas as configurações importadas tenham um prefixo comum (o que reduzirá o risco de colisões de namespace).

parse_yaml sample.yml "CONF_"

rendimentos:

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

Observe que as configurações anteriores em um arquivo podem ser referidas por configurações posteriores:

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

## output
output:
   debug: $global_debug

Outro uso interessante é primeiro analisar um arquivo padrão e, em seguida, as configurações do usuário, que funcionam, pois as últimas configurações substituem as primeiras:

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

3
Stefan legal! Seria incrível se também pudesse transformar a -notação yaml em matrizes nativas do bash!
Quickshiftin

3
Isso deve ser bastante fácil se você alterar a linha printf no script awk. Observe que o bash não tem suporte para matrizes associativas multidimensionais, portanto, você acaba com uma matriz + uma única chave por valor. Hmm, provavelmente deve mover este para github ...
Stefan Farestam

5
Isso espera o recuo yml padrão de 2 espaços. Se você estiver usando 4 espaços, as variáveis ​​terão dois sublinhados como delimitador, por exemplo, em global__debugvez de global_debug.
K0pernikus

3
Oi vaab - Embora eu esteja certo de que muitos leitores gostariam de analisar arquivos YAML reais do shell, não está claro (pelo menos para mim) qual seria o resultado. Com esse script, dei uma facada no problema e defini um subconjunto que possui um mapeamento razoável em variáveis ​​padrão. Certamente não há pretensão de ter resolvido o problema maior de analisar arquivos YAML reais.
Stefan Farestam 22/10

3
Apenas imprime a saída na tela. Como você acessaria os valores posteriormente?
sattu 04/12/19

96

eu escrevi shyaml em python para necessidades de consulta YAML na linha de comando do shell.

Visão geral:

$ pip install shyaml      ## installation

Arquivo YAML do exemplo (com recursos complexos):

$ 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 em loop mais complexa em valores complexos:

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

Alguns pontos-chave:

  • todos os tipos de YAML e esquisitices de sintaxe são manipulados corretamente, como multilinhas, seqüências de caracteres entre aspas, sequências em linha ...
  • \0 saída acolchoada está disponível para manipulação sólida de múltiplas linhas.
  • notação pontilhada simples para selecionar subvalores (ou seja: subvalue.maintaineré uma chave válida).
  • o acesso pelo índice é fornecido às seqüências (ou seja: subvalue.things.-1é o último elemento da subvalue.thingssequência).
  • acesso a todos os elementos de sequência / estruturas de uma só vez para uso em loops de bash.
  • você pode gerar uma subparte inteira de um arquivo YAML como ... YAML, que combina bem para outras manipulações com shyaml.

Mais amostras e documentação estão disponíveis na página shyaml github ou na página shyaml PyPI .


1
Isso é incrível! Seria ótimo se houvesse um sinalizador para ignorar os valores de yaml que estão em branco na saída. No momento, ele gera "nulo". Eu estou usando-o juntamente com envdir a saída de um arquivo de janela de encaixe-compor a envdircat docker-compose.yml | shyaml get-value api.environment | grep -v null | awk -F': ' '{print $2 > ("envdir/" $1)}'
Grilo Falante

@JiminyCricket Use a página de edições do github! Eu ficaria feliz em pelo menos acompanhar isso. ;)
vaab

1
Infelizmente, shyamlé ridiculamente lento
nowox 19/11/2015

42

yq é um processador YAML de linha de comando leve e portátil

O objetivo do projeto é ser o jq ou sed dos arquivos yaml.

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

Como exemplo (roubado diretamente da documentação ), dado um arquivo sample.yaml de:

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

então

yq r sample.yaml bob.*.cats

irá produzir

- bananas
- apples

ele é apenas falta os recursos de filtragem
Antonin

formulae.brew.sh/formula/yq tem 26.679 instalações instaladas no ano passado.
dustinevan

1
@Antonin Não tenho certeza se é isso que você quer dizer, mas parece que agora tem alguns recursos de filtragem: mikefarah.gitbook.io/yq/usage/path-expressions
bmaupin

32

É possível passar um pequeno script para alguns intérpretes, como Python. Uma maneira fácil de fazer isso usando Ruby e sua biblioteca YAML é o seguinte:

$ 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

, onde dataé um hash (ou matriz) com os valores de yaml.

Como um bônus, ele analisará a matéria da frente de Jekyll muito bem.

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

1
é utilizável? você colocou yaml por eco para intérprete de rubi. mas como deve usar essa variável no restante do script bash?
Znik 30/09

Sim, é utilizável. A RUBY_SCRIPTvariável é um script ruby ​​que pode ser gravado em um arquivo (executado com ruby -ryaml <rubyscript_filename>). Ele contém a lógica para transformar o texto de entrada em algum texto de saída, armazenando internamente o conteúdo na datavariável. O eco gera um texto yaml, mas você pode usar cat <yaml_filename>para canalizar o conteúdo de um arquivo.
Rafael

Sinto muito, mas não vejo isso no exemplo acima. Na primeira variável, RUBY_SCRIPT mantém o código do interpretador de ruby. O próximo echo -e simula todos os dados yaml, isto é, por pilha redirecionada para o interpretador de ruby. Isso chama código ruby ​​como script embutido e, finalmente, é impresso para gerar exemplos de variáveis ​​'a' e 'b'. Então, onde está o carregamento variável no bash para seu código executável restante? Eu vejo apenas uma solução alternativa. colocando o ruby ​​outout no arquivo_ temporário, que deve conter linhas: variable = 'value', e depois carregá-lo no bash por '. Arquivo temporário'. mas isso é solução alternativa, não resolução.
Znik 17/10

1
@Znik Uma vez que você tenha algo no stdout, produzido por algo alimentado com stdin, o resto depende das mãos do bash coder (e como um lembrete, se você precisar stdoutalimentar a variável, não precisará confiar em arquivos temporários! use x=$(...)ou mesmo read a b c < <(...)). Portanto, essa é uma solução válida quando você sabe exatamente o que deseja buscar no arquivo YAML e sabe como escrever as linhas ruby ​​para acessar esses dados. Mesmo que seja difícil, é uma prova completa do conceito da ideia IMHO. É verdade, no entanto, que ele não fornece uma abstração completa do bash.
vaab

Sim, ele é. Você é rígido. Obrigado por esse truque. Usar uma variável é simples. mas muitos wariables não são. truque com lista variável de leitura <<(execução para stdout) é muito útil :)
Znik

23

Dado que Python3 e PyYAML são dependências bastante fáceis de serem encontradas hoje em dia, o seguinte pode ajudar:

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

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

Eu amo shyaml, mas em sistemas desconectados isso salva a vida. Também deve funcionar com grande parte do python2, por exemplo, RHEL.
rsaw

2
Talvez use yaml.safe_loadcomo é mais seguro. pyyaml.org/wiki/PyYAMLDocumentation
Jordan Stewart

12

aqui uma versão estendida da resposta 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 versão suporta o - notação e a notação curta de dicionários e listas. A seguinte entrada:

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

produz esta saída:

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 você pode ver o - itens são numerados automaticamente para obter nomes de variáveis ​​diferentes para cada item. Nobash não existem matrizes multidimensionais, essa é uma maneira de contornar. Vários níveis são suportados. Para contornar o problema de espaços em branco à direita mencionados por @briceburg, deve-se colocar os valores entre aspas simples ou duplas. No entanto, ainda existem algumas limitações: A expansão dos dicionários e listas pode produzir resultados incorretos quando os valores contêm vírgulas. Além disso, estruturas mais complexas, como valores que abrangem várias linhas (como ssh-keys), ainda não são suportadas.

Algumas palavras sobre o código: O primeiro sedcomando expande a forma abreviada de dicionários { key: value, ...}para regular e os converte para um estilo yaml mais simples. A segunda sedchamada faz o mesmo para a notação curta de listas e é convertida [ entry, ... ]em uma lista detalhada com a -notação. A terceira sedchamada é a original que lidava com dicionários normais, agora com a adição de lidar com listas -e recuos. A awkparte introduz um índice para cada nível de indentação e aumenta quando o nome da variável está vazio (ou seja, ao processar uma lista). O valor atual dos contadores é usado em vez do vname vazio. Ao subir um nível, os contadores são zerados.

Edit: Eu criei um repositório do github para isso.


11

Difícil dizer porque depende do que você deseja que o analisador extraia do seu documento YAML. Para casos simples, que você pode ser capaz de usar grep, cut, awketc. Para a análise mais complexa que você precisaria usar um full-blown analisar biblioteca, como Python PyYAML ou YAML :: Perl .


11

Acabei de escrever um analisador que chamei Yay! ( Yaml não é Yamlesque! ), Que analisa Yamlesque , um pequeno subconjunto de YAML. Portanto, se você está procurando um analisador YAML 100% compatível com o Bash, não é isso. No entanto, para citar o OP, se você deseja um arquivo de configuração estruturado que seja o mais fácil possível para um usuário não técnico editar semelhante ao YAML, isso pode ser interessante.

Ele é inspirado pela resposta anterior, mas grava matrizes associativas ( sim, requer o Bash 4.x ) em vez de variáveis ​​básicas. Isso é feito de uma maneira que permite que os dados sejam analisados ​​sem o conhecimento prévio das chaves, para que o código controlado por dados possa ser gravado.

Assim como os elementos da matriz chave / valor, cada matriz possui uma keysmatriz que contém uma lista de nomes de chaves, uma childrenmatriz que contém nomes de matrizes filho e uma parentchave que se refere ao seu pai.

Este é um exemplo 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

Aqui está um exemplo mostrando como usá-lo:

#!/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

quais saídas:

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

E aqui está o analisador:

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

Há alguma documentação no arquivo de origem vinculado e abaixo está uma breve explicação do que o código faz.

A yay_parsefunção primeiro localiza o inputarquivo ou sai com um status de saída igual a 1. Em seguida, determina o conjunto de dados prefix, especificado explicitamente ou derivado do nome do arquivo.

Ele grava bashcomandos válidos em sua saída padrão que, se executados, definem matrizes que representam o conteúdo do arquivo de dados de entrada. O primeiro deles define a matriz de nível superior:

echo "declare -g -A $prefix;"

Observe que as declarações de matriz são associativas ( -A), que é um recurso da versão 4. do Bash. As declarações também são globais ( -g) para que possam ser executadas em uma função, mas estejam disponíveis para o escopo global como o yayauxiliar:

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

Os dados de entrada são processados ​​inicialmente com sed. Ele descarta linhas que não correspondem à especificação do formato Yamlesque antes de delimitar os campos válidos do Yamlesque com um caractere ASCII File Separator e remover aspas duplas ao redor do 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" |

As duas expressões são semelhantes; eles diferem apenas porque o primeiro seleciona valores entre aspas, enquanto que o segundo escolhe valores não citados.

O Separador de Arquivos (28 / hex 12 / octal 034) é usado porque, como um caractere não imprimível, é improvável que esteja nos dados de entrada.

O resultado é canalizado para o awkqual processa sua entrada uma linha por vez. Ele usa o caractere FS para atribuir cada campo a uma variável:

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

Todas as linhas têm um recuo (possivelmente zero) e uma chave, mas nem todas têm um valor. Ele calcula um nível de recuo para a linha que divide o comprimento do primeiro campo, que contém o espaço em branco à esquerda, por dois. Os itens de nível superior sem nenhum recuo estão no nível de recuo zero.

Em seguida, ele decide o que prefixusar para o item atual. É isso que é adicionado a um nome de chave para criar um nome de matriz. Existe um root_prefixpara a matriz de nível superior, definida como o nome do conjunto de dados e um sublinhado:

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

A parent_keyé a chave no nível de indentação acima do nível de indentação da linha atual e representa a coleção da qual a linha atual faz parte. Os pares chave / valor da coleção serão armazenados em uma matriz com seu nome definido como a concatenação de prefixe parent_key.

Para o nível superior (nível de recuo zero), o prefixo do conjunto de dados é usado como chave pai, portanto, ele não possui prefixo (está definido como ""). Todas as outras matrizes são prefixadas com o prefixo raiz.

Em seguida, a chave atual é inserida em um array (interno do awk) contendo as chaves. Essa matriz persiste durante toda a sessão do awk e, portanto, contém chaves inseridas por linhas anteriores. A chave é inserida na matriz usando seu recuo como índice da matriz.

keys[indent] = key;

Como essa matriz contém chaves de linhas anteriores, quaisquer chaves com um ralador de nível de recuo além do nível de recuo da linha atual são removidas:

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

Isso deixa a matriz de chaves que contém o conjunto de chaves da raiz no nível de recuo 0 até a linha atual. Ele remove chaves obsoletas que permanecem quando a linha anterior foi recuada mais profundamente que a linha atual.

A seção final gera os bashcomandos: uma linha de entrada sem valor inicia um novo nível de recuo (uma coleção na linguagem YAML) e uma linha de entrada com um valor adiciona uma chave à coleção atual.

O nome da coleção é a concatenação da linha atual prefixe parent_key.

Quando uma chave tem um valor, uma chave com esse valor é atribuída à coleção atual como esta:

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

A primeira instrução gera o comando para atribuir o valor a um elemento de matriz associativa nomeado após a chave e a segunda gera o comando para adicionar a chave à keyslista delimitada por espaço da coleção :

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

Quando uma chave não tem um valor, uma nova coleção é iniciada assim:

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

A primeira instrução gera o comando para adicionar a nova coleção à childrenlista delimitada por espaço da coleção atual e a segunda gera o comando para declarar uma nova matriz associativa para a nova coleção:

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

Toda a saída de yay_parsepode ser analisada como comandos bash pelos comandos bash evalou sourceinternos.


Você já pensou em fazer deste um projeto no GitHub? Ou já é?
Daniel

@daniel, está no GitHub, mas não em seu próprio repositório - você pode encontrá-lo aqui . Veja os diretórios examplese usr/lib, Estes estão ligados na minha resposta à pergunta. Se houver interesse, eu poderia dividi-lo em seu próprio repositório.
23616 starfry

4
Muitos elogios no YAY. No começo, eu o reescrevi como puro bash, mas depois não consegui me conter e o reimplementei como um analisador básico com suporte para matrizes e estruturas aninhadas que não podem pisar nos nomes uns dos outros. Está em github.com/binaryphile/y2s .
Php binário

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

útil apenas para configurações planas. não é aplicável ao yaml estruturado. outro, como evitar o uso de file.sh temporário?
Znik 30/09

5

Outra opção é converter o YAML em JSON e, em seguida, use jq para interagir com a representação JSON para extrair informações ou editá-las.

Eu escrevi um script simples do bash que contém essa cola - veja o projeto Y2J no GitHub


2

Se você precisar de um único valor, poderá usar uma ferramenta que converta seu documento YAML em JSON e alimente jq, por exemplo yq.

Conteúdo de sample.yaml:

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

Exemplo:

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

1

Sei que isso é muito específico, mas acho que minha resposta pode ser útil para determinados usuários.
Se você possui nodee npminstalou em sua máquina, você pode usá-lo js-yaml.
Primeira instalação:

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

então no seu script bash

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

Além disso, se você estiver usando, jqpoderá fazer algo assim

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

Porque js-yamlconverte um arquivo yaml em um literal json string. Você pode usar a string com qualquer analisador json no seu sistema unix.


1

Se você tem python 2 e PyYAML, pode usar este analisador que escrevi chamado parse_yaml.py . Algumas das coisas mais simples que faz é permitir que você escolha um prefixo (caso tenha mais de um arquivo com variáveis ​​semelhantes) e escolha um único valor em um arquivo yaml.

Por exemplo, se você possui esses arquivos 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

Você pode carregar os dois sem conflito.

$ 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 até a cereja escolhe os valores que você deseja.

$ 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

Você pode usar um equivalente de yq que está escrito em golang:

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

retorna:

62.0.3

0

Você também pode considerar o uso do Grunt (o JavaScript Task Runner). Pode ser facilmente integrado ao shell. Ele suporta a leitura de arquivos YAML ( grunt.file.readYAML) e JSON ( grunt.file.readJSON).

Isso pode ser conseguido através da criação de uma tarefa em Gruntfile.js(ou Gruntfile.coffee), por exemplo:

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

};

depois, a partir do shell, basta executar grunt foo(verifique grunt --helpas tarefas disponíveis).

Além disso, você pode implementar exec:footask ( grunt-exec) com variáveis ​​de entrada passadas de sua task ( foo: { cmd: 'echo bar <%= foo %>' }) para imprimir a saída no formato que desejar, e canalizá-la para outro comando.


Também existe uma ferramenta semelhante ao Grunt, chamada gulp com o plug - in adicional gulp-yaml .

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

Uso da amostra:

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 obter mais opções para lidar com o formato YAML , consulte o site do YAML em busca de projetos, bibliotecas e outros recursos disponíveis, que podem ajudá-lo a analisar esse formato.


Outras ferramentas:

  • Jshon

    analisa, lê e cria JSON


0

Sei que minha resposta é específica, mas se alguém já possui o PHP e o Symfony instalados, pode ser muito útil usar o analisador YAML do Symfony.

Por exemplo:

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

Aqui eu simplesmente utilizava var_dumpa saída do array analisado, mas é claro que você pode fazer muito mais ... :)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.