O jq funciona passando os dados JSON de entrada por uma única expressão (escrita como um encadeamento de filtros) para obter os dados transformados pretendidos.
jq
A linguagem jq é implementada pelo programa jq.
Este programa disponibiliza várias opções de linha de comandos práticas para controlar como a entrada é consumida e como a saída é apresentada.
Nos exemplos abaixo vais encontrar:
-n ou --null-input
Normalmente, o programa jq recebe um ficheiro para ler, ou envias dados para a sua entrada.
A opção --null-input permite-te gerar dados JSON sem qualquer entrada.
-c ou --compact-output
Por omissão, o jq formata a saída de forma legível.
É extremamente útil para os humanos ver os dados quando estão bem formatados.
No entanto, isso não é necessário para as máquinas: a opção --compact-output remove os espaços em branco da formatação para minimizar o tamanho do JSON resultante.
-f filename ou --from-file filename
Lê o programa jq a partir de filename em vez de o fornecer na linha de comandos.
Tanto o sed como o awk usam a opção -f para o mesmo fim.
Vais ver isto a ser usado nos scripts de teste dos exercícios de prática.
Consulta o manual para mais detalhes sobre todas as opções.
Os filtros também são conhecidos como expressões.
Um filtro recebe uma entrada e produz uma saída.
Tal como trabalhas numa shell Unix, podes juntar filtros com um pipe | para ligar a saída de um filtro à entrada de outro.
.
Este é o filtro mais simples.
Limita-se a passar a sua entrada para a sua saída.
Por exemplo, o jq formata a saída de forma legível por omissão, por isso passar JSON pelo filtro . dá-te uma saída bem formatada sem esforço!
$ echo '[1, 2, 3]' | jq '.'
[
1,
2,
3
]
Esta será uma introdução rápida ao trabalho com arrays. Vamos abordar este tópico com mais detalhe mais adiante.
Acede-se aos elementos de um array com parênteses retos, e a indexação começa em zero.
$ echo '[10, 20, 30]' | jq '.[1]'
20
Um filtro pode construir um array envolvendo uma expressão em [ e ]
com uma lista de elementos conhecida:
jq -n '[1, 2, 3]'
para recolher um fluxo de elementos: por exemplo,
range é uma função que produz um fluxo de números
$ jq -n 'range(10; 70; 15)'
10
25
40
55
Usar [] recolhe os resultados da expressão num array
$ jq -c -n '[range(10; 70; 15)]'
[10,25,40,55]
A vírgula não é apenas sintaxe que separa elementos de um array. A vírgula é um operador que junta fluxos.
Por exemplo, [1, 2, 3] é um filtro que usa o construtor de array [] para recolher o resultado da junção das três expressões 1, 2 e 3.
Reparaste nos pontos e vírgulas em range(10; 70; 15) acima?
Como as vírgulas têm um propósito específico na linguagem jq, as funções que recebem vários argumentos usam pontos e vírgulas para separar os argumentos.
Uma introdução rápida aos objetos.
Tal como em muitas linguagens de programação, usa pontos para aceder às propriedades de um objeto
$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
Também se podem usar parênteses retos para objetos, mas nesse caso são necessárias aspas para literais de string. Este é um método para trabalhar com chaves que contêm espaços.
$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"
Podes construir um objeto com {} e pares key: value.
As aspas não são necessárias à volta de chaves que sejam strings "simples".
jq -n '{question: (6 * 9), answer: 42}'
produz
{
"question": 54,
"answer": 42
}
Para tratar a chave como uma expressão, tens de a envolver em parênteses (o seguinte também produz o mesmo que acima).
echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
É bastante comum querer extrair um subconjunto de chaves de um objeto grande.
Por exemplo, para extrair id e name de
{
"id": 101,
"name": "alpha widget",
"specifications": {...}
}
Podíamos escrever
{id: .id, name: .name}
Mas isto é tão comum que existe uma sintaxe abreviada para o fazer:
{id, name}
Por exemplo, dado um file.json com
{
"key1": "value1",
"key2": [5, 15, 25]
}
Vamos calcular o comprimento do array key2:
$ jq '.key2 | length' file.json
3
Estamos a encadear a saída da expressão .key2 como entrada de length, que, como seria de esperar, produz o número de elementos do array.
Este é um aspeto do jq que exige alguma habituação:
a maioria das funções (mas não todas) comporta-se como filtros, em que passas dados para a entrada do filtro, e não como argumento.
Neste exemplo, os dados JSON de entrada são ignorados e não têm impacto na saída:
$ echo '{"answer": 42}' | jq '6 * 9'
54
Um filtro pode produzir mais do que um valor.
Por exemplo, o filtro .[] produz cada elemento de um array como um valor separado:
$ jq -n -c '[1, 2, 3]'
[1,2,3]
$ jq -n -c '[1, 2, 3] | .[]'
1
2
3
Encadear um filtro destes noutro vai executar o 2.º filtro para cada valor:
$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6
Isto é como uma iteração implícita.
Assim que compreenderes esta técnica, vais perceber que filtros jq muito poderosos podem ser bastante concisos.
Os parênteses servem para agrupar subexpressões e impor a ordem das operações, tal como noutras linguagens.
No jq, a necessidade deles pode parecer um pouco surpreendente.
Por exemplo, digamos que queremos construir um array com 2 elementos: a raiz quadrada de 9; e e elevado à potência 1.
As duas expressões individuais são 9 | sqrt e 1 | exp.
Esperamos que a saída seja o array [3, 2.7...]
$ jq -n '[ 9|sqrt, 1|exp ]'
[
20.085536923187668,
2.718281828459045
]
Porque é que não obtivemos o que esperávamos? O jq interpreta isto assim:
[ ((9|sqrt), 1) | exp ]
O jq constrói um fluxo de dois elementos (3 e 1), que são ambos entregues a exp.
Temos de garantir que exp recebe apenas um número como entrada.
Por outras palavras, temos de garantir que o pipe é avaliado antes da vírgula.
$ jq -n '[ 9|sqrt, (1|exp) ]'
[
3,
2.718281828459045
]
Do manual
O jq suporta o mesmo conjunto de tipos de dados que o JSON: números, strings, Boolean, arrays, objetos (que, em linguagem JSON, são hashes com apenas chaves de string) e "null".
Vais aprender mais sobre estes nos exercícios seguintes.
Os espaços em branco não são significativos no jq.
Usa espaços, tabulações e novas linhas como achares adequado para formatar o teu código.
Desconhecemos a existência de guias de estilo para jq.
Os valores no jq são imutáveis.
Os filtros que modificam um valor produzem um novo valor.
Isto implica que o jq não tem variáveis globais: vais ter de te habituar a passar estado de um filtro para outro.
Os valores false e null são considerados falsos. Qualquer outro valor
(incluindo o número zero e a string, o array ou o objeto vazios) é verdadeiro.
Sem entrar em grandes detalhes (as funções serão tema de outro exercício), aqui ficam algumas funções incorporadas úteis:
Dado um array como entrada, produz o número de elementos do array.
$ jq -n '[10, 20, 30, 40] | length'
4
Este operador faz coisas diferentes consoante o tipo dos seus operandos: soma números, concatena strings, acrescenta elementos a arrays, funde objetos.
$ jq -c -n '
3 + 4,
"foo" + "bar",
["a", "b"] + ["c"],
{"m": 10} + {"n": 20}
'
7
"foobar"
["a","b","c"]
{"m":10,"n":20}
O add é uma função que recebe um array e devolve um elemento com todos os elementos somados de acordo com as regras do +.
[1, 2, 3] | add produz 6.
Dado um array como entrada e um filtro como argumento, produz um array em que o filtro é aplicado a cada elemento
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
Dado algum valor de entrada e um filtro como argumento:
null, mas mesmo nenhuma saída)Por exemplo, dados alguns números, seleciona os que são divisíveis por 3
$ jq -n 'range(10) | select(. % 3 == 0)'
0
3
6
9
Lembra-te de que range produz um fluxo de números.
O select será invocado uma vez por cada número.
Só os números que "passam" a expressão são produzidos.
Muitas vezes precisas de selecionar elementos de um array. Há algumas formas de o fazer.
Com o valor de entrada ["Anne", "Bob", "Cathy", "Dave"], seleciona os nomes com comprimento 4.
usa map e select em conjunto
map(select(length == 4))
divide o array em elementos, aplica select a esse fluxo e recolhe os resultados
[ .[] | select(length == 4) ]
Os comentários começam com o caráter # e continuam até ao fim da linha.