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 keys
matriz que contém uma lista de nomes de chaves, uma children
matriz que contém nomes de matrizes filho e uma parent
chave 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_parse
função primeiro localiza o input
arquivo 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 bash
comandos 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 yay
auxiliar:
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 awk
qual 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 prefix
usar para o item atual. É isso que é adicionado a um nome de chave para criar um nome de matriz. Existe um root_prefix
para 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 prefix
e 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 bash
comandos: 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 prefix
e 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 à keys
lista 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 à children
lista 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_parse
pode ser analisada como comandos bash pelos comandos bash eval
ou source
internos.