Percursos
/
jq
jq
/
Programa
/
Noções Básicas
No

Noções Básicas em jq

1 exercício

Sobre Noções Básicas

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.

Uma introdução rápida às opções de linha de comandos do 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.

Filtros e pipes

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.

O filtro de identidade: .

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
]

Arrays

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 é um operador

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.

Objetos

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]}'
Note

É 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}

Encadeamentos

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.

Caution

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.

Os filtros podem ignorar a sua entrada

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

Os filtros podem produzir fluxos de dados

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.

Parênteses

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
]

Tipos

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.

Espaços em branco

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.

Valores imutáveis

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.

"Veracidade"

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.

Funções e operadores

Sem entrar em grandes detalhes (as funções serão tema de outro exercício), aqui ficam algumas funções incorporadas úteis:

  • length

    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.

  • map

    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]
    
  • select

    Dado algum valor de entrada e um filtro como argumento:

    • se o filtro aplicado ao argumento resultar num valor verdadeiro, produz o valor de entrada sem alterações
    • caso contrário, não produz nada (não o valor 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) ]
      

Comentários

Os comentários começam com o caráter # e continuam até ao fim da linha.

Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Noções Básicas