Basico do jq: Como Processar JSON no Shell

Basico do jq: Como Processar JSON no Shell

O Que Voce Vai Aprender

  • Como formatar JSON de curl ou respostas de API em formato legivel
  • O vocabulario essencial do jq que voce realmente precisa: select, map, -r e mais
  • Padroes seguros que evitam as armadilhas comuns: aspas perdidas vazando em variaveis do shell, propagacao de null e escritas de arquivo quebradas

Referencia Rapida (padroes de producao)

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

Ambiente Assumido

  • jq 1.6 ou posterior (Ubuntu 20.04+ apt install jq e suficiente; diferencas da 1.7 indicadas inline)
  • Referencia oficial: jq Manual

O Que e o jq?

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

  • Segue o modelo de pipe Unix: stdin -> filtro -> stdout
  • Expressoes de filtro sao avaliadas da esquerda para a direita, assim como pipes do shell
  • Tem um sistema de tipos real: numeros, strings, arrays, objetos, null e booleanos

Como Instalar o jq?

O caminho mais rapido e o gerenciador de pacotes do seu sistema. Se voce usar jq em scripts de CI, fixe a versao 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, voce precisa do epel-release primeiro. Para runners de CI, o binario estatico unico da pagina oficial de releases colocado em /usr/local/bin/jq e a opcao mais portavel.

Como Ler um Filtro Basico?

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

1. Formatacao

$ 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 expressao jq. '...' e a regra.

Como Trabalhar com Objetos e Arrays?

Combine .[] (explodir array), , (avaliacao 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} e abreviacao para {id: .id, tag: .tag}. Funciona apenas para chaves de identificador simples.

Descer com seguranca 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 condicao e verdadeira. O padrao e 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"}

Condicoes numericas 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 padrao "filtrar depois extrair"

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

Mantenha a ordem: explodir -> filtrar -> extrair. E mais facil 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 sao as operacoes 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) e equivalente a [.[] | f]. A diferenca: .[] | 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 numero de chaves, para null retorna 0. Sensivel ao tipo.

keys: listar as chaves

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

keys e ordenado, keys_unsorted preserva a ordem de insercao. Use keys quando precisar de saida deterministica para testes.

Como Formatar a Saida? (-r / -c)

jq . e para humanos, -r (raw) e para variaveis do shell, e -c (compacto) e para encadear com ferramentas orientadas a linhas. Oito em cada dez bugs de jq vem de escolher o modo de saida errado.

-r: remover aspas da string para saida raw

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

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

Quando voce atribui a uma variavel do shell com NAME=$(...), sempre use -r. Caso contrario, o literal "linny" (com aspas) vai para a variavel.

-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 {}. E a ponte entre ferramentas JSON e ferramentas Unix tradicionais.

Saida 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 tabulacao. Empacote valores em um array primeiro, depois encadeie para o formatador.

-r so remove aspas de strings. Numeros e objetos ainda saem como JSON. Para transformar um array em linhas, voce deve explodi-lo com .[] primeiro.

Padroes Praticos para o Trabalho Diario

Formatacao de respostas de API, agregacao de logs e atualizacao de arquivos de configuracao: tres padroes que voce vai reutilizar para sempre. Memorize os templates e adapte.

Buscar com curl e extrair campos especificos

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

-sS significa silencioso em sucesso, barulhento em erro. A interpolacao 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 configuracao

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

|= e reatribuicao, += e adicionar e atribuir. Combine com caminhos profundos: .items |= map(. * 2).

Quais Sao as Armadilhas Comuns?

Tres armadilhas pegam quase todo mundo: propagacao de null, erros de tipo e passar valores do shell para filtros. Ler a mensagem de erro antes de pesquisar corta o tempo de depuracao pela metade.

1. Cannot iterate over null

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

Solucao: fornecer um padrao com // [].

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

2. Incorporar variaveis 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 numeros, arrays ou booleanos.

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

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

Uma string nao e um array. Use o filtro type para confirmar o que voce realmente tem.

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

4. Controlar um script com o codigo de saida

jq -e retorna codigo de saida 1 quando a saida e 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"'

Proximas Leituras