Jq: processando JSON no terminal

1. Introdução ao jq e instalação

O jq é um processador de JSON leve e flexível para linha de comando, essencial para qualquer profissional que trabalhe com Bash/Shell Script. Ele permite extrair, filtrar, transformar e formatar dados JSON diretamente no terminal, sem necessidade de linguagens mais pesadas como Python ou Node.js.

No contexto de shell script, o jq brilha ao processar saídas de APIs REST, arquivos de configuração, logs estruturados e qualquer fluxo de dados JSON. Sua filosofia é similar à de ferramentas Unix clássicas como sed, awk e grep, mas especificamente projetada para JSON.

Instalação

Linux (Debian/Ubuntu):

sudo apt update && sudo apt install jq

Linux (RHEL/CentOS/Fedora):

sudo yum install jq          # CentOS 7
sudo dnf install jq          # Fedora/RHEL 8+

macOS (Homebrew):

brew install jq

Verificação:

jq --version
# Saída esperada: jq-1.7.1 (ou similar)

Primeiro teste:

echo '{"chave":"valor"}' | jq .
# Saída:
# {
#   "chave": "valor"
# }

2. Estrutura básica de comandos e filtros

A sintaxe fundamental do jq é: jq 'filtro' entrada. A entrada pode ser um arquivo ou pipe.

Filtro identidade (.)

O ponto simples retorna o JSON intacto, formatado:

echo '{"nome":"João","idade":30}' | jq .

Acesso a chaves

echo '{"pessoa":{"nome":"Maria","endereco":{"cidade":"SP"}}}' | jq '.pessoa.nome'
# "Maria"

echo '{"pessoa":{"nome":"Maria","endereco":{"cidade":"SP"}}}' | jq '.pessoa.endereco.cidade'
# "SP"

Acesso a arrays

echo '[10,20,30,40]' | jq '.[0]'    # 10
echo '[10,20,30,40]' | jq '.[-1]'   # 40 (último)
echo '[10,20,30,40]' | jq '.[]'     # 10 20 30 40 (cada elemento em linha)

3. Filtros avançados de seleção

Seleção condicional com select()

echo '[{"nome":"Ana","idade":25},{"nome":"João","idade":35},{"nome":"Maria","idade":28}]' | \
jq '.[] | select(.idade > 28)'
# {
#   "nome": "João",
#   "idade": 35
# }

Operadores lógicos

echo '[{"nome":"Ana","idade":25,"cidade":"SP"},{"nome":"João","idade":35,"cidade":"RJ"}]' | \
jq '.[] | select(.idade > 25 and .cidade == "RJ")'
# {
#   "nome": "João",
#   "idade": 35,
#   "cidade": "RJ"
# }

Filtro recursivo (..) e acesso opcional (.a?)

# Encontrar todos os valores numéricos em qualquer profundidade
echo '{"a":1,"b":{"c":2,"d":{"e":3}}}' | jq '.. | numbers'
# 1
# 2
# 3

# Acesso opcional (não gera erro se chave não existir)
echo '{"a":1}' | jq '.b?'
# null (sem erro)

4. Transformação e construção de dados

Criação de objetos

echo '{"nome":"Carlos","sobrenome":"Silva","idade":40}' | \
jq '{nome_completo: (.nome + " " + .sobrenome), idade: .idade}'
# {
#   "nome_completo": "Carlos Silva",
#   "idade": 40
# }

Criação de arrays

echo '{"x":10,"y":20,"z":30}' | jq '[.x, .y, .z]'
# [10, 20, 30]

Operador pipe (|) e funções

echo '[5,3,8,1,9]' | jq 'add'          # 26 (soma)
echo '[5,3,8,1,9]' | jq 'length'       # 5
echo '{"a":1,"b":2}' | jq '. | length' # 2 (número de chaves)

# map() - aplica filtro a cada elemento
echo '[1,2,3,4]' | jq 'map(. * 2)'
# [2, 4, 6, 8]

5. Formatação de saída e opções úteis

Saída compacta vs. indentada

# Compacta (útil para pipes)
echo '{"a":1,"b":2}' | jq -c '.'
# {"a":1,"b":2}

# Indentação personalizada
echo '{"a":1,"b":2}' | jq --tab '.'     # usa tab
echo '{"a":1,"b":2}' | jq --indent 4 '.' # 4 espaços

Saída raw (-r)

Remove as aspas ao redor de strings, essencial para usar com outros comandos:

echo '{"nome":"João"}' | jq -r '.nome'
# João (sem aspas)

Lendo filtros de arquivo (-f)

# filtro.jq contendo: {nome, idade}
echo '{"nome":"Ana","idade":30,"cidade":"SP"}' | jq -f filtro.jq
# {
#   "nome": "Ana",
#   "idade": 30
# }

Silenciar erros com -e

Útil em scripts onde o erro não deve interromper o fluxo:

echo '{"a":1}' | jq -e '.b' 2>/dev/null || echo "Chave não encontrada"

6. Trabalhando com funções embutidas

Inspeção de objetos

echo '{"nome":"João","idade":30,"cidade":"SP"}' | jq 'keys'
# ["cidade", "idade", "nome"]

echo '{"nome":"João","idade":30}' | jq 'has("idade")'
# true

Estatísticas em arrays

echo '[5,3,8,1,9,3,5]' | jq '{min: min, max: max, sort: sort, unique: unique}'
# {
#   "min": 1,
#   "max": 9,
#   "sort": [1, 3, 3, 5, 5, 8, 9],
#   "unique": [1, 3, 5, 8, 9]
# }

Conversão de tipos

echo '"123"' | jq 'tonumber'           # 123 (número)
echo '42' | jq 'tostring'              # "42" (string)
echo '["a","b","c"]' | jq 'join("-")'  # "a-b-c"
echo '"a-b-c"' | jq 'split("-")'       # ["a","b","c"]

Conversão entre string JSON e objeto

echo '{"dados":"{\"valor\":42}"}' | jq '.dados | fromjson'
# {
#   "valor": 42
# }

echo '{"valor":42}' | jq 'tojson'
# "{\"valor\":42}"

7. Exemplos práticos no terminal

Extrair dados de API REST

# Supondo uma API que retorna usuários
curl -s "https://api.exemplo.com/usuarios" | jq '.[] | {nome: .nome, email: .email}'

# Com saída raw para usar em loop
curl -s "https://api.exemplo.com/usuarios" | jq -r '.[] | "\(.nome): \(.email)"'

Filtrar logs JSON

# log.json contendo: {"nivel":"erro","mensagem":"Falha na conexão"}
#                 {"nivel":"info","mensagem":"Serviço iniciado"}
jq 'select(.nivel == "erro")' log.json

Combinar com xargs

# Baixar arquivos listados em JSON
echo '["doc1.pdf","doc2.pdf","doc3.pdf"]' | \
jq -r '.[]' | xargs -I {} wget "https://cdn.exemplo.com/{}"

Atualizar arquivos JSON (com sponge)

# Altere o campo "versao" para "2.0" no arquivo config.json
jq '.versao = "2.0"' config.json | sponge config.json
# Requer: sudo apt install moreutils

8. Boas práticas e armadilhas comuns

Aspas simples vs. duplas

Sempre use aspas simples para o filtro no shell, para evitar que o shell interprete caracteres especiais ($, \, "):

# Correto
echo '{"nome":"João"}' | jq '.nome'

# Incorreto (pode falhar)
echo '{"nome":"João"}' | jq ".nome"

Se precisar usar variáveis do shell, feche as aspas simples:

nome="João"
echo '{"nome":"João"}' | jq '.nome == "'$nome'"'

Escapando caracteres especiais

Dentro do filtro (aspas simples), use \" para aspas duplas e \\ para barra invertida:

echo '{"path":"C:\\Users"}' | jq '.path'

Diferença entre jq .arquivo e jq -f arquivo

  • jq .arquivo — processa o arquivo de dados chamado .arquivo
  • jq -f arquivo — lê o filtro do arquivo chamado arquivo

Nunca confunda: jq filtro.jq dados.json funciona, mas jq .filtro.jq tenta ler um arquivo chamado .filtro.jq.

Performance com arquivos grandes

Para JSONs enormes (gigabytes), use --stream para processar sem carregar tudo em memória:

jq --stream 'select(.[0] | length == 2) | .[1]' megaarquivo.json

Referências