Básico do jq: Como Processar JSON no Shell

Básico do jq: Como Processar JSON no Shell

O Que Você Vai Aprender

  • Como formatar JSON de curl ou respostas de API em formato legível
  • O vocabulário essencial do jq que você realmente precisa: select, map, -r e mais
  • Padrões seguros que evitam as armadilhas comuns: aspas perdidas vazando em variáveis do shell, propagação de null e escritas de arquivo quebradas

Referência Rápida (padrões de produção)

  • Apenas formatar -> jq .
  • Extrair um valor -> jq -r '.field'
  • Explodir um array em um JSON por linha -> jq -c '.[]'
  • Filtrar por condição -> jq '.[] | select(.status=="ok")'
  • Evitar propagação de null -> jq '.field // empty'

Ambiente Assumido

  • jq 1.6 ou posterior (Ubuntu 20.04+ apt install jq é suficiente; diferenças da 1.7 indicadas inline)
  • Referência oficial: jq Manual

O Que é o jq?

jq é uma linguagem de filtro e ferramenta de linha de comando com reconhecimento de JSON. Diferente de forçar grep/awk em JSON, jq entende a estrutura, o que significa menos desastres com regex em pipelines de API, agregação de logs e scripts de CI.

  • Segue o modelo de pipe Unix: stdin -> filtro -> stdout
  • Expressões de filtro são avaliadas da esquerda para a direita, assim como pipes do shell
  • Tem um sistema de tipos real: números, strings, arrays, objetos, null e booleanos

Como Instalar o jq?

O caminho mais rápido é o gerenciador de pacotes do seu sistema. Se você usar jq em scripts de CI, fixe a versão com jq --version porque o comportamento difere entre releases principais.

# Ubuntu / Debian
$ sudo apt update && sudo apt install jq

# RHEL / Rocky / AlmaLinux
$ sudo dnf install jq

# macOS (Homebrew)
$ brew install jq

# Verificar
$ jq --version

Em CentOS 7 mais antigo, você precisa do epel-release primeiro. Para runners de CI, o binário estático único da página oficial de releases colocado em /usr/local/bin/jq é a opção mais portável.

Como Ler um Filtro Básico?

Um filtro jq é uma transformação aplicada à sua entrada. O filtro vazio . é a identidade (formata e retorna). Use .field para ler uma chave de objeto e .[] para explodir um array.

1. Formatação

$ echo '{"name":"linny","age":3}' | jq .
{
  "name": "linny",
  "age": 3
}

2. Extrair uma chave

$ echo '{"name":"linny","age":3}' | jq '.name'
"linny"

3. Iterar sobre um array

$ echo '[{"id":1},{"id":2}]' | jq '.[]'
{"id":1}
{"id":2}

Sempre envolva filtros em aspas simples. Aspas duplas deixam o shell interpretar $ e crases, o que corrompe sua expressão jq. '...' é a regra.

Como Trabalhar com Objetos e Arrays?

Combine .[] (explodir array), , (avaliação paralela) e | (encadear filtros). Parece exatamente com grep | awk.

Extrair um campo de cada elemento

$ echo '[{"id":1,"tag":"a"},{"id":2,"tag":"b"}]' \
  | jq '.[].tag'
"a"
"b"

Construir uma tupla de campos selecionados

$ echo '[{"id":1,"tag":"a"},{"id":2,"tag":"b"}]' \
  | jq '.[] | {id, tag}'
{"id":1,"tag":"a"}
{"id":2,"tag":"b"}

{id, tag} é abreviação para {id: .id, tag: .tag}. Funciona apenas para chaves de identificador simples.

Descer com segurança em campos aninhados

$ echo '{"a":{"b":{"c":42}}}' | jq '.a.b.c'
42

# Chaves ausentes produzem null; o ? suprime erros de iteracao
$ echo '{}' | jq '.a.b?.c?'
null

Como Filtrar com select?

select(condition) passa adiante apenas valores onde a condição é verdadeira. O padrão é explodir um array com .[] primeiro, depois encadear com select. Pense em SQL WHERE.

Filtro de igualdade

$ echo '[{"s":"ok"},{"s":"ng"},{"s":"ok"}]' \
  | jq '.[] | select(.s=="ok")'
{"s":"ok"}
{"s":"ok"}

Condições numéricas e compostas

$ jq '.[] | select(.score >= 80 and .active)'
$ jq '.[] | select(.tag=="a" or .tag=="b")'
$ jq '.[] | select(.tag | startswith("v1"))'

Verificar a presenca de uma chave

$ echo '[{"a":1},{"b":2}]' \
  | jq '.[] | select(has("a"))'
{"a":1}

O padrão "filtrar depois extrair"

jq '.[] | select(.status=="error") | .message'

Mantenha a ordem: explodir -> filtrar -> extrair. É mais fácil depurar passo a passo. Adicione // empty se quiser pular nulls silenciosamente.

O Que map, length e keys Fazem?

map(f) aplica f a cada elemento de um array, length retorna o tamanho e keys lista as chaves de um objeto. Estas são as operações em massa para trabalhar em um array como um todo.

map: transformar um array

$ echo '[1,2,3]' | jq 'map(. * 10)'
[
  10,
  20,
  30
]

map(f) é equivalente a [.[] | f]. A diferença: .[] | f transmite um elemento por vez, enquanto map(f) retorna o array intacto.

length: contar elementos

$ echo '[{"id":1},{"id":2},{"id":3}]' | jq 'length'
3

Para strings retorna a contagem de caracteres, para objetos o número de chaves, para null retorna 0. Sensível ao tipo.

keys: listar as chaves

$ echo '{"a":1,"c":3,"b":2}' | jq 'keys'
[
  "a",
  "b",
  "c"
]

keys é ordenado, keys_unsorted preserva a ordem de inserção. Use keys quando precisar de saída determinística para testes.

Como Formatar a Saída? (-r / -c)

jq . é para humanos, -r (raw) é para variáveis do shell, e -c (compacto) é para encadear com ferramentas orientadas a linhas. Oito em cada dez bugs de jq vêm de escolher o modo de saída errado.

-r: remover aspas da string para saída raw

$ echo '{"name":"linny"}' | jq '.name'
"linny"

$ echo '{"name":"linny"}' | jq -r '.name'
linny

Quando você atribui a uma variável do shell com NAME=$(...), sempre use -r. Caso contrário, o literal "linny" (com aspas) vai para a variável.

-c: um JSON por linha

$ echo '[{"id":1},{"id":2}]' | jq -c '.[]'
{"id":1}
{"id":2}

Combine -c com loops while read line ou xargs -I {}. É a ponte entre ferramentas JSON e ferramentas Unix tradicionais.

Saída TSV / CSV (@tsv / @csv)

$ echo '[{"id":1,"name":"a"},{"id":2,"name":"b"}]' \
  | jq -r '.[] | [.id, .name] | @tsv'
1	a
2	b

@csv coloca aspas em valores string; @tsv usa separadores de tabulação. Empacote valores em um array primeiro, depois encadeie para o formatador.

-r só remove aspas de strings. Números e objetos ainda saem como JSON. Para transformar um array em linhas, você deve explodi-lo com .[] primeiro.

Padrões Práticos para o Trabalho Diário

Formatação de respostas de API, agregação de logs e atualização de arquivos de configuração: três padrões que você vai reutilizar para sempre. Memorize os templates e adapte.

Buscar com curl e extrair campos específicos

$ curl -sS "https://api.example.com/users" \
  | jq -r '.users[] | "\(.id)\t\(.name)"'

-sS significa silencioso em sucesso, barulhento em erro. A interpolação de string "\(.id)\t\(.name)" permite escolher qualquer separador.

Agregar com group_by

$ jq '[.[] | {status}] | group_by(.status) | map({status: .[0].status, count: length})'

group_by(f) retorna um array de arrays agrupados por f. Envolva com map({key: ..., count: length}) para produzir uma tabela de contagem.

Reescrever parte de um arquivo de configuração

# Atualizar versao em package.json
$ jq '.version = "1.2.3"' package.json > package.json.tmp \
  && mv package.json.tmp package.json
# Alternativa mais segura com sponge
$ jq '.version = "1.2.3"' package.json | sponge package.json

Adicionar a um array

$ echo '{"items":[1,2]}' | jq '.items += [3]'
{
  "items": [
    1,
    2,
    3
  ]
}

|= é reatribuição, += é adicionar e atribuir. Combine com caminhos profundos: .items |= map(. * 2).

Quais São as Armadilhas Comuns?

Três armadilhas pegam quase todo mundo: propagação de null, erros de tipo e passar valores do shell para filtros. Ler a mensagem de erro antes de pesquisar corta o tempo de depuração pela metade.

1. Cannot iterate over null

# .users esta ausente
$ echo '{}' | jq '.users[]'
jq: error (at <stdin>:1): Cannot iterate over null (null)

Solução: fornecer um padrão com // [].

$ echo '{}' | jq '.users // [] | .[]'
# (sem saida, exit 0)

2. Incorporar variáveis do shell em um filtro

# Ruim: aspas colidem
$ KEY="name"
$ jq ".$KEY" file.json   # apos expansao do shell vira '."name"' em alguns shells

# Bom: passe via --arg
$ jq --arg key "$KEY" '.[$key]' file.json

--arg passa o valor como uma string; --argjson passa como JSON. Use --argjson para números, arrays ou booleanos.

3. "Parece um array mas .[] falha"

$ echo '"abc"' | jq '.[]'
jq: error (at <stdin>:1): Cannot iterate over string ("abc")

Uma string não é um array. Use o filtro type para confirmar o que você realmente tem.

$ echo '"abc"' | jq 'type'
"string"

4. Controlar um script com o código de saída

jq -e retorna código de saída 1 quando a saída é false ou null, permitindo encadear com || para fluxo de controle.

$ echo '{"ok":false}' | jq -e '.ok' || echo "falhou"
false
falhou

Templates copiar e colar

# Formatar e paginar
jq . file.json | less

# Capturar em uma variavel (sempre use -r)
NAME=$(curl -sS "$URL" | jq -r '.name')

# Transmitir elementos de array como JSON de 1 linha para xargs
curl -sS "$URL" | jq -c '.users[]' \
  | xargs -I {} sh -c 'echo "USER: {}"'

# Extracao null-safe com padrao
jq -r '.path.to.value // "DEFAULT"'

Próximas Leituras