Peça a uma API a lista de utilizadores, exporte os eventos de autenticação de um serviço na cloud ou abra os alertas de um EDR: a resposta vem quase sempre em JSON. E o JSON, ao contrário de um log de texto, tem estrutura — objetos, arrays, vários níveis de aninhamento. O grep e o sed só veem caracteres; partir um JSON com eles é frágil e acaba mal à primeira chaveta fora do sítio.
O jq entende essa estrutura e permite filtrar, transformar e remodelar dados com uma linguagem própria, compacta e expressiva. Neste artigo percorremos a linguagem pela ordem do vídeo — campos, arrays, pipes e select, construção de saída, funções de transformação e opções da linha de comandos —, sempre com o mesmo ficheiro de exemplo.
Veja o guia em vídeo
Prefere ver antes de experimentar? No canal HJFR-IT no YouTube mostramos o jq opção a opção, com o resultado de cada comando no ecrã — narrado em português e com legendas.
O que faz o jq
O jq lê documentos JSON (de ficheiros ou da entrada padrão), aplica-lhes um filtro e escreve o resultado. Cada filtro recebe um valor e produz zero, um ou vários valores, e os filtros encaixam uns nos outros. É a ligação entre o curl e o resto de um script: o curl vai buscar a resposta, o jq tira dela o que interessa.
jq [OPÇÕES] 'FILTRO' [FICHEIRO...]
comando | jq [OPÇÕES] 'FILTRO'
Escreva o filtro sempre entre plicas, para que a shell não interprete os $, os | nem as aspas. Nos exemplos usamos um users.json fictício, a resposta de uma API de identidade; o campo falhas conta os logins falhados nas últimas 24 horas:
{
"name": "hjfr",
"endereco": {"cidade": "Lisboa", "pais": "PT"},
"users": [
{"name": "aluno", "role": "user", "falhas": 2, "mfa": true, "ip": "192.0.2.10", "email": "aluno@example.com"},
{"name": "ana", "role": "admin", "falhas": 0, "mfa": true, "ip": "192.0.2.11", "email": "ana@example.com"},
{"name": "backup", "role": "svc", "falhas": 41, "mfa": false, "ip": "203.0.113.66", "email": "backup@example.com"},
{"name": "rui", "role": "admin", "falhas": 37, "mfa": false, "ip": "203.0.113.66", "email": "rui@example.com"},
{"name": "sara", "role": "user", "falhas": 5, "mfa": true, "ip": "198.51.100.7", "email": "sara@example.com"}
]
}
O básico: o ponto e o acesso a campos
O filtro mais simples é um ponto, que significa «a entrada inteira». Parece inútil, mas o jq formata e indenta o JSON: uma resposta que chegou espremida numa só linha fica legível, e com cores no terminal.
jq . users.json— o documento inteiro, formatado.jq '.name' users.json— o valor do camponame:"hjfr".jq '.endereco.cidade' users.json— os campos encadeiam-se para descer níveis:"Lisboa". Um campo inexistente devolvenull, sem erro.jq -r '.name' users.json— o mesmo valor, cru:hjfr.
Repare: por omissão o jq escreve JSON, e uma cadeia em JSON vem entre aspas. Para usar o valor num script, quase sempre o queremos cru — é o que faz o -r (de raw), por exemplo em ORG=$(jq -r '.name' users.json). O ponto para navegar e o -r para o valor limpo aparecem em quase todos os comandos jq do dia a dia.
Arrays: iterador, índices e fatias
A peça central do jq é o iterador .[]: desmonta um array e produz cada elemento em separado. Quase tudo o resto se constrói a partir dele.
.users[]— emite os cinco objetos, um a um, já fora do array..users[0]— o primeiro elemento (os índices começam em 0;.users[-1]é o último)..users[2:4]— uma fatia com os elementos 2 e 3 (o fim é exclusivo), devolvida como array..users[].name— iterador mais campo: o nome de todos os utilizadores de uma vez.
$ jq '.users[].name' users.json
"aluno"
"ana"
"backup"
"rui"
"sara"
length, keys e has
length— elementos de um array, chaves de um objeto ou caracteres de uma cadeia:.users | lengthdevolve5.keys— as chaves de um objeto, por ordem alfabética (keys_unsortedmantém a original). Ideal para explorar um JSON desconhecido.has("chave")—trueoufalse, consoante a chave exista:.users[0] | has("password")confirma que a API não expõe palavras-passe.
Pipes e select: consultas completas
Tal como a shell, o jq tem o seu próprio |, que passa cada resultado de um filtro ao seguinte: .users[] | .name lê-se «para cada utilizador, dá-me o nome». A função que mais se combina com ele é select(condição), que deixa passar só os valores em que a condição é verdadeira. Encadeando etapas, obtém-se uma consulta completa — como numa base de dados, mas sobre JSON:
# Contas com mais de 30 logins falhados (objetos completos)
$ jq '.users[] | select(.falhas > 30)' users.json
# Emails dos administradores
$ jq '.users[] | select(.role == "admin") | .email' users.json
"ana@example.com"
"rui@example.com"
Este padrão — iterar, selecionar, extrair — resolve a maioria das tarefas reais. As condições aceitam ==, !=, <, >, and, or e not.
Construir saída: objetos, arrays e texto
O jq também constrói JSON novo, na forma de que o passo seguinte precisa:
.users[] | {user: .name, mail: .email}— as chavetas criam um objeto só com os campos escolhidos, com os nomes que quiser.{name, falhas}é o atalho de{name: .name, falhas: .falhas}.[.users[].name]— os parênteses retos fazem o inverso do iterador: recolhem os resultados num único array."\(.name): \(.falhas)"— interpolação: dentro de uma cadeia,\(expressão)insere o valor no texto.
$ jq -r '.users[] | "\(.name): \(.falhas)"' users.json
aluno: 2
ana: 0
backup: 41
rui: 37
sara: 5
Exportar para CSV com @csv
@csv converte um array numa linha CSV, com as cadeias entre aspas e escapadas (@tsv usa tabulações). Use-o sempre com -r, senão cada linha sai como uma cadeia JSON cheia de \"; acrescente > contas.csv e o relatório está pronto.
$ jq -r '.users[] | [.name, .role, .falhas] | @csv' users.json
"aluno","user",2
"ana","admin",0
"backup","svc",41
"rui","admin",37
"sara","user",5
Funções de transformação
A biblioteca de funções sobre arrays faz do jq um verdadeiro motor de processamento: agrega, ordena e calcula sem que seja preciso escrever um programa.
map, sort_by, group_by e unique
map(f)— aplicafa cada elemento e devolve um array novo:.users | map(.falhas)dá[2, 0, 41, 37, 5].sort_by(.campo)— ordena objetos por um campo, do menor para o maior.group_by(.campo)— junta em subarrays os objetos com o mesmo valor.unique— ordena e remove duplicados:[.users[].ip] | uniquedevolve os quatro IP distintos.
# Que contas partilham o mesmo IP de origem? (-c: resultado numa linha)
$ jq -c '.users | group_by(.ip) | map({ip: .[0].ip, contas: map(.name)})' users.json
[{"ip":"192.0.2.10","contas":["aluno"]},{"ip":"192.0.2.11","contas":["ana"]},{"ip":"198.51.100.7","contas":["sara"]},{"ip":"203.0.113.66","contas":["backup","rui"]}]
add, cálculos e outras funções
add soma os números de um array (ou concatena cadeias e arrays) e, dividido por length, dá a média: [.users[].falhas] | add devolve 85 e [.users[].falhas] | add / length devolve 17. Há muitas mais:
minemax— o menor e o maior valor (min_byemax_bypara objetos).reverse— inverte a ordem;sort_by(.falhas) | reversedá um «top» descendente.flatten— achata arrays aninhados:[["ana","rui"],["backup"]]passa a["ana","rui","backup"].to_entries— converte um objeto num array de pares{"key", "value"};from_entriesfaz o inverso.
Opções da linha de comandos
Para além do -r, estas são as opções que afinam a forma como o jq lê e escreve:
-c(compact) — cada resultado numa só linha: o formato JSON Lines, um objeto por linha, ideal paragrepouwc -l.-s(slurp) — junta toda a entrada num único array, em vez de tratar cada documento à parte. Útil para combinar vários ficheiros ou um registo em JSON Lines.-n(null input) — não lê entrada; o filtro constrói o JSON do zero.--arg nome valor— passa um valor da shell para o jq como a variável$nome, sempre como cadeia e sem aspas dentro de aspas (--argjsonpara números ou JSON).-e(exit status) — código de saída pelo último resultado: 0 se verdadeiro, 1 sefalseounull, 4 se não houver resultado.
# Quantos logins falhados há no registo em JSON Lines?
jq -s 'map(select(.ok == false)) | length' auth.jsonl
# Gerar um alerta com um valor vindo de uma variável
jq -n --arg ip "$IP" '{alerta: "brute-force", ip: $ip}'
# Avisar se o administrador rui não tiver MFA
jq -e '.users[] | select(.name == "rui") | .mfa' users.json > /dev/null || echo "ALERTA: admin sem MFA"
jq em cibersegurança
Triagem de registos na linha de comandos. Registos de auditoria da cloud, alertas de EDR e eventos de autenticação saem hoje em JSON ou JSON Lines. Com o jq, perguntas como «que contas tiveram mais de 30 falhas?» ou «que IP aparece em várias contas?» respondem-se em minutos, no próprio servidor e sem copiar dados sensíveis para outro lado. O group_by(.ip) do exemplo revela o padrão de um password spraying: duas contas atacadas a partir do mesmo endereço.
Extrair indicadores de comprometimento. select com -r produz listas limpas de IP, contas ou hashes, prontas para uma lista de bloqueio ou para um sort | uniq -c. Com -c, cada evento continua a ser JSON válido numa linha; com @csv, a cronologia do incidente passa diretamente para o relatório.
Scripts seguros com –arg. Colar variáveis da shell dentro do filtro, como em jq ".users[] | select(.name == \"$CONTA\")", é um convite à injeção: um valor manipulado altera a própria consulta. Com --arg, o valor entra sempre como dado, nunca como código. Junte-lhe o -e para que as verificações automáticas — um administrador sem MFA, por exemplo — falhem de forma visível.
Experimente no simulador interativo
O simulador usa o mesmo users.json — cinco contas, duas atacadas a partir do mesmo IP — e um pequeno registo auth.jsonl. À esquerda vê cada etapa do filtro e o documento desenhado como árvore, com os nós apanhados realçados, os suspeitos a laranja e os descartados esbatidos; à direita, a saída no terminal. Clique nos filtros e opções, ou use Demonstrar tudo para percorrer a sequência completa do vídeo; Anterior e Seguinte avançam passo a passo e Repor estado volta ao início.
Erros comuns e boas práticas
- Pedir um campo a um array:
.users.nametermina comCannot index array with "name". Itere primeiro:.users[].name. - Esquecer o
-r: as aspas do JSON acabam dentro de variáveis e nomes de ficheiro. - Pôr o filtro entre aspas duplas: a shell expande
$e\antes de o jq os ver. Filtro entre plicas; valores externos com--arg. - Confundir
.[]com[ ]: o iterador emite resultados soltos; os parênteses retos recolhem-nos num array, que é o quelengthousort_byesperam. - Tratar JSON Lines como um só documento: sem
-s, cada linha é processada à parte elengthconta as chaves de cada evento, não os eventos. - Partir JSON com
grepesed: funciona até a ordem dos campos ou a indentação mudarem. Use o jq para a estrutura e ogreppara o texto que dele sai.
Conclusão
O jq transforma o JSON de obstáculo em matéria-prima: o ponto para navegar, o iterador para percorrer arrays, o pipe e o select para consultar, as chavetas e a interpolação para construir, e meia dúzia de funções e opções para agregar e automatizar. Domine o padrão iterar, selecionar, extrair e terá resolvido a maioria das tarefas reais. Para ir buscar o JSON a uma API, veja o nosso artigo sobre o curl e o wget; para pesquisar no texto que o jq produz, o guia do comando grep.
Workshop — Linha de Comandos Linux
Quer passar da leitura à prática, com exercícios reais e acompanhamento? No workshop Linha de Comandos Linux da HJFR trabalha estes e outros comandos da série — da administração do dia a dia à resposta a incidentes.
Este artigo faz parte da série «Comandos Linux, um a um» do blog HJFR — fique atento aos próximos comandos.
![Imagem destacada do artigo sobre o comando jq: o filtro jq '.[]' em destaque, com a frase «JSON domado na linha de comandos»](https://www.hjfr-info.pt/wp-content/uploads/2026/10/destaque-comando-jq-1.png)