Parsing de YAML e TOML no Bash
1. Introdução ao problema: por que parsear YAML e TOML no Bash?
O Bash, apesar de poderoso para automação de tarefas no terminal, possui limitações significativas quando o assunto é lidar com formatos de dados estruturados. Diferente de linguagens como Python ou JavaScript, o Bash não oferece mapas associativos complexos, listas aninhadas ou estruturas de dados tipadas. Arrays no Bash são unidimensionais e não suportam chaves arbitrárias de forma nativa (embora o declare -A ofereça arrays associativos a partir do Bash 4).
No entanto, cenários reais frequentemente exigem a leitura de arquivos de configuração em YAML (Docker Compose, Kubernetes, Ansible) ou TOML (pyproject.toml, Cargo.toml). Surge então a necessidade de parsear esses formatos diretamente de scripts Bash, seja para automatizar deploys, extrair metadados ou integrar ferramentas.
Existem duas abordagens principais: utilizar ferramentas externas especializadas (como yq e tomlq) ou implementar parsers caseiros com grep, sed e awk. Cada abordagem tem seus prós e contras, que exploraremos a seguir.
2. Parsing de YAML com ferramentas externas
A ferramenta mais robusta para lidar com YAML no Bash é o yq, um port do jq (JSON) para YAML. Sua sintaxe é familiar para quem já usa jq.
# Instalação via snap ou brew
sudo snap install yq
# ou
brew install yq
# Exemplo: extrair o nome de um serviço de um docker-compose.yml
yq e '.services.web.image' docker-compose.yml
Outra abordagem é converter YAML para JSON usando yaml2json e depois processar com jq:
# Convertendo YAML para JSON
yaml2json config.yml | jq '.database.host'
Exemplo prático com um docker-compose.yml:
# docker-compose.yml
version: '3'
services:
web:
image: nginx:latest
ports:
- "80:80"
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: secret
# Extraindo todas as imagens
yq e '.services.*.image' docker-compose.yml
# Saída: nginx:latest
# postgres:13
3. Parsing de YAML sem dependências externas (abordagens caseiras)
Para YAML simples (apenas chave-valor, sem aninhamento profundo), é possível usar grep, sed e awk:
# YAML simples: config.yaml
app_name: meu_app
version: 1.0
debug: true
# Script para parsear
parse_simple_yaml() {
local prefix=$2
local s='[[:space:]]*'
local w='[a-zA-Z0-9_]*'
local fs=$(echo @|tr @ '\034')
sed -ne "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\"\n", vn, $2, $3);
}
}'
}
eval $(parse_simple_yaml config.yaml)
echo $app_name # meu_app
Limitações importantes: indentação irregular, listas, strings multilinha e comentários podem quebrar o parser. Para YAML complexo, sempre prefira yq.
4. Parsing de TOML com ferramentas externas
O tomlq (parte do ecossistema yq) é a ferramenta mais direta:
# Instalação
pip install tomlq # ou via yq
# Extraindo dependências de pyproject.toml
tomlq e '.project.dependencies[]' pyproject.toml
O remarshal é outro conversor versátil que suporta múltiplos formatos:
# Instalação
pip install remarshal
# Convertendo TOML para JSON
remarshal -if toml -of json pyproject.toml | jq '.tool.poetry.dependencies'
Exemplo com Cargo.toml:
# Cargo.toml
[package]
name = "meu_projeto"
version = "0.1.0"
[dependencies]
serde = "1.0"
tokio = { version = "1", features = ["full"] }
# Extraindo nome do pacote
tomlq e '.package.name' Cargo.toml
# Saída: meu_projeto
5. Parsing de TOML com Bash puro (quando não há ferramentas)
Para situações onde não é possível instalar dependências, um parser TOML básico pode ser implementado:
parse_toml_simple() {
local file="$1"
local current_section=""
local key value
while IFS= read -r line; do
# Ignorar comentários e linhas vazias
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ -z "$line" ]] && continue
# Detectar seção
if [[ "$line" =~ ^\[([^\]]+)\]$ ]]; then
current_section="${BASH_REMATCH[1]}"
current_section="${current_section//./_}"
continue
fi
# Parsear chave=valor
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_-]*)[[:space:]]*=[[:space:]]*\"(.*)\"[[:space:]]*$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
if [[ -n "$current_section" ]]; then
printf "%s_%s=\"%s\"\n" "$current_section" "$key" "$value"
else
printf "%s=\"%s\"\n" "$key" "$value"
fi
elif [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_-]*)[[:space:]]*=[[:space:]]*(true|false|[0-9]+)\s*$ ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
if [[ -n "$current_section" ]]; then
printf "%s_%s=%s\n" "$current_section" "$key" "$value"
else
printf "%s=%s\n" "$key" "$value"
fi
fi
done < "$file"
}
eval $(parse_toml_simple config.toml)
echo $package_name # valor extraído
Limitações: arrays inline, tabelas aninhadas [a.b.c] e datas não são suportados nessa implementação simplificada.
6. Técnicas avançadas e boas práticas
Uma técnica poderosa é combinar source com arquivos convertidos para variáveis de ambiente:
# Converter YAML para formato de variáveis e source
yq e '.. | select(tag == "!!str" or tag == "!!int") | path | join("_") + "=" + .' config.yml > /tmp/env_vars.sh
source /tmp/env_vars.sh
Tratamento de erros e fallback:
get_value() {
local key="$1"
local default="$2"
local value
value=$(yq e ".$key" config.yml 2>/dev/null)
if [[ "$value" == "null" || -z "$value" ]]; then
echo "$default"
else
echo "$value"
fi
}
# Uso
DB_HOST=$(get_value "database.host" "localhost")
Performance: para arquivos pequenos (< 100KB), scripts puros podem ser mais rápidos. Para arquivos grandes ou estruturas complexas, ferramentas externas são significativamente mais eficientes.
7. Exemplo completo: script de deploy que lê configuração YAML e TOML
#!/bin/bash
# Script de deploy universal
DEPLOY_CONFIG="deploy.conf"
# Detecção automática do formato
detect_format() {
local file="$1"
case "$file" in
*.yml|*.yaml) echo "yaml" ;;
*.toml) echo "toml" ;;
*) echo "unknown" ;;
esac
}
# Função genérica de parse
parse_config() {
local file="$1"
local format=$(detect_format "$file")
case "$format" in
yaml)
yq e "$2" "$file" 2>/dev/null || echo ""
;;
toml)
tomlq e "$2" "$file" 2>/dev/null || echo ""
;;
*)
echo "Formato não suportado: $file" >&2
return 1
;;
esac
}
# Extrair valores
APP_NAME=$(parse_config "$DEPLOY_CONFIG" '.app.name')
APP_VERSION=$(parse_config "$DEPLOY_CONFIG" '.app.version')
BUILD_CMD=$(parse_config "$DEPLOY_CONFIG" '.build.command')
DEPLOY_CMD=$(parse_config "$DEPLOY_CONFIG" '.deploy.command')
# Executar build
echo "Deployando $APP_NAME v$APP_VERSION"
eval "$BUILD_CMD" && eval "$DEPLOY_CMD"
8. Considerações finais e referências
Para YAML, o yq é a ferramenta mais madura e recomendada. Para TOML, tomlq ou remarshal oferecem boa cobertura. Scripts puros são viáveis apenas para casos muito simples e controlados. Em ambientes onde Python está disponível, usar python3 -c com bibliotecas padrão (yaml, tomllib) é uma alternativa robusta e portável.
A escolha entre ferramentas externas e scripts puros deve considerar: complexidade dos dados, necessidade de performance, portabilidade do ambiente e tolerância a falhas. Para scripts de produção, sempre prefira ferramentas especializadas.
Referências
- yq - Documentação Oficial — Guia completo de instalação e uso do yq para processamento de YAML no terminal.
- jq - Manual do jq — Documentação oficial do jq, frequentemente usado em conjunto com yq para consultas avançadas.
- remarshal - Conversor Universal — Ferramenta que converte entre YAML, TOML, JSON e outros formatos, ideal para pipelines.
- tomlq - Parser TOML — Seção da documentação do yq que explica como usar tomlq para arquivos TOML.
- Bash Arrays Associativos - Guia Avançado — Documentação GNU sobre arrays associativos no Bash, útil para entender as limitações nativas.
- Parsing YAML with Bash - Artigo Técnico — Discussão no Stack Overflow com várias abordagens para parsear YAML no Bash.
- Python inline para parsing robusto — Documentação do módulo tomllib do Python 3.11+, alternativa moderna para parsing de TOML.