Regex com o comando sed para analisar o texto json

15

Eu tenho este texto json:

{
    "buildStatus" : {
        "status" : "ERROR",
        "conditions" : [{
                "status" : "OK",
                "metricKey" : "bugs"
            }, {
                "status" : "ERROR",
                "metricKey" : "test_success_density"
            }, {
                "status" : "OK",
                "metricKey" : "vulnerabilities"
            }
        ],
        "periods" : []
    }
}

Eu quero extrair o status geral do buildStatus, ou seja, a saída esperada foi "ERRO"

"buildStatus" : {
    "status" : "ERROR",
    ....
}

Eu tentei a expressão sed abaixo, mas não está funcionando, ela retorna OK:

status= sed -E 's/.*\"buildStatus\":.*\"status\":\"([^\"]*)\",.*/\1/' jsonfile

O que estou fazendo de errado?

user1876040
fonte

Respostas:

16

Não analise estruturas de dados aninhadas complexas como JSON ou XML com expressões regulares, use um analisador JSON adequado, como jshon.

Primeiro você precisa instalá-lo:

sudo apt-get install jshon

Em seguida, é necessário fornecer os dados JSON para analisar via entrada padrão, para que você possa redirecionar a saída de outro comando para lá com um pipe ( |) ou redirecionar um arquivo para ele ( < filename).

Os argumentos necessários para extrair os dados que você deseja são assim:

jshon -e "buildStatus" -e "status" -u
  • -e "buildStatus" escolhe o elemento com o índice "buildStatus" no dicionário de nível superior.
  • -e "status" escolhe o elemento com o índice "status" no dicionário de segundo nível escolhido acima.
  • -u converte os dados selecionados de JSON em dados simples (ou seja, aqui ele remove as aspas ao redor da string)

Portanto, o comando que você executa, dependendo de onde você obtém os dados, se parece com um deles:

jshon -e "buildStatus" -e "status" -u < YOUR_INPUT_FILE
YOUR_JSON_PRODUCING_COMMAND | jshon -e "buildStatus" -e "status" -u

Para saber mais jshon, você pode ler sua página de manual acessível on-line aqui ou simplesmente digitando man jshon.

Byte Commander
fonte
6
Há também jq:jq -r .buildStatus.status
muru 23/12/16
@HTNW Eu nunca gostei dessa resposta, porque "tag única XML aberta" (que é a pergunta que faz) é uma linguagem comum (e você pode, em princípio, criar um analisador XML completo usando expressões regulares para combinar tags, comentários, cdata seções e usando uma pilha simples para lidar com o contexto aninhado). No entanto, a linguagem regular mais "interessante" no JSON é uma string literal.
Random832
10

Trabalho para jq:

jq -r '.["buildStatus"]["status"]' file.json

Pode ser reduzido para:

jq -r '.buildStatus.status' file.json

-r( --raw-output) gera a string sem jsonformatação, sem aspas.

Exemplo:

% cat file.json                   
{
    "buildStatus" : {
        "status" : "ERROR",
        "conditions" : [{
                "status" : "OK",
                "metricKey" : "bugs"
            }, {
                "status" : "ERROR",
                "metricKey" : "test_success_density"
            }, {
                "status" : "OK",
                "metricKey" : "vulnerabilities"
            }
        ],
        "periods" : []
    }
}

% jq -r '.["buildStatus"]["status"]' file.json
ERROR

% jq -r '.buildStatus.status' file.json       
ERROR

Se ainda não estiver instalado, instale-o por (disponível no repositório Universe):

sudo apt-get install jq 
heemail
fonte
8

Como já foi mencionado, é preferível analisar dados estruturados complexos com a API apropriada. O Python possui um jsonmódulo para isso, que eu pessoalmente uso bastante em meus scripts, e é muito fácil extrair os campos desejados da seguinte maneira:

$ python -c 'import sys,json;print json.load(sys.stdin)["buildStatus"]["status"]' <  input.txt
ERROR

O que acontece aqui é que redirecionamos o arquivo de entrada para o stdin do python e o lemos com json.load(). Isso se torna um dicionário python com a chave "buildStatus" e contém outro dicionário python com a chave "status". Portanto, estamos apenas imprimindo o valor de uma chave em um dicionário que é armazenado em outro dicionário. Relativamente simples.

Além da simplicidade, outra vantagem é que o python e essa API estão pré-instalados e vêm com o Ubuntu por padrão.

Sergiy Kolodyazhnyy
fonte
6

Na verdade, você pode fazer isso sed, mas recomendo que você use uma linguagem mais sofisticada que possua ferramentas escritas para manipular dados JSON. Você pode tentar perl ou python, por exemplo.

Agora, no seu exemplo simples, tudo o que você deseja é a primeira ocorrência "status", para que você possa fazer:

$ sed -nE '/status/{s/.*:\s*"(.*)",/\1/p;q}' file.json 
ERROR

O truque é usar -npara evitar a impressão; se a linha coincidir com status( /status/), você remove tudo, exceto a parte desejada s/.*:\s*"(.*)",/\1/, ppinta a linha e quit.


Pessoalmente, acho esse comando grep equivalente muito mais simples:

$ grep -m1 -oP '"status"\s*:\s*"\K[^"]+' file.json 
ERROR

Ou este:

$ perl -ne 'if(s/.*"status"\s*:\s*"([^"]+).*/$1/){print;exit}' file.json 
ERROR

Sério, se você planeja analisar arquivos JSON, não tente fazer isso manualmente. Use um analisador JSON adequado.

Terdon
fonte
ou este:grep -m 1 status file.json | tr -cd '[[:alnum:]]:' | cut -f2 -d':'
slowko 23/12/16
11
@ user1876040 de nada. Lembre-se de aceitar uma das respostas (eu recomendo o ByteCommander's , ele é uma solução melhor) para que a pergunta possa ser marcada como respondida).
Ter23
6

Não estou dizendo que você deveria usar sed(acho que alguém me deu um voto negativo apenas por não escrever uma advertência obrigatória), mas, se você precisar procurar algo na próxima linha, buildStatuscomo parece estar tentando em sua própria tentativa, precisará dizer sedpara ler a próxima linha com o Ncomando

$ sed -rn '/buildStatus/N;s/.*buildStatus.*\n.*: "(.*)",/\1/p' file
ERROR

Notas:

  • -n não imprima nada até pedirmos
  • -ruse ERE (igual a -E)
  • /buildStatus/N encontre esse padrão e leia a próxima linha também
  • s/old/new/substitua oldpornew
  • .* qualquer número de caracteres na linha
  • \n nova linha
  • : "(.*)",salve os caracteres que ocorrem entre : "e",
  • \1 referência anterior ao padrão salvo
  • p imprimir a parte em que trabalhamos
Zanna
fonte
0

Há uma explicação típica do porquê sede ferramentas de processamento de fluxo de texto semelhantes não estão bem equipadas para analisar dados estruturados, como JSON e XML. Eu não tenho isso em mãos, mas está lá fora, e acredito que o ponto é que as expressões necessárias em quase todas as situações, menos a menor, rapidamente se tornam muito complexas, enquanto ferramentas alternativas criadas especificamente para analisar a estrutura são mais elegante, legível e eficiente na mesma análise.

Como muru colocou em um comentário , jqdeve ser a ferramenta certa para o trabalho. Também posso garantir que, pessoalmente, estou muito empolgado em vê-lo substituir várias vezes, quando tentei analisar os mesmos dados sem quase nenhum ou com grande sucesso. Ele ainda contém uma grande capacidade de formatação e controle da saída. Eu prefiro jsontoolpor um motivo ou mais do que eu esqueço atualmente.

O Byte Commander parece recomendar jshonem outra resposta . Eu não usei essa ferramenta, mas ela me lembra xmlstarlete sua sintaxe, também com uma apresentação personalizável para a saída.

Pysis
fonte
Você provavelmente está falando de stackoverflow.com/a/1732454/2072269 #
muru
3
Considere melhorar sua resposta, mostrando um exemplo de como jsontoolpode ser usado no caso específico do OP.
Sergiy Kolodyazhnyy 23/12/16
Lol @muru, correto, essa é uma das postagens que tentam impedir os usos de analisar XML / JSON com Regex! Eu estava mais recomendando jqque muru e heemayl descrevessem que já têm exemplos, e apenas postando o raciocínio por trás disso: askubuntu.com/a/863948/230721
Pysis
0

Apenas outra ferramenta Json chamada json ( https://github.com/trentm/json )

$ json buildStatus.status < file.json
ERROR

Este estudo de caso é enganoso: parece que as ferramentas não estão funcionando. Você também pode usar jsonpara alterar arquivos json:

$ json -e 'this.buildStatus.status="not error"' < file.json > new.json

ou até ...

$ json -e 'this.buildStatus.status="no errors"' < file.json | json -e 'this.buildStatus.status
no errors

documentação em: http://trentm.com/json/


se não estiver instalado:

  • nó de instalação
  • e sudo npm install -g json

fonte