Analisando JSON com ferramentas Unix

879

Estou tentando analisar JSON retornado de uma solicitação de curl, assim:

curl 'http://twitter.com/users/username.json' |
    sed -e 's/[{}]/''/g' | 
    awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}'

O exemplo acima divide o JSON em campos, por exemplo:

% ...
"geo_enabled":false
"friends_count":245
"profile_text_color":"000000"
"status":"in_reply_to_screen_name":null
"source":"web"
"truncated":false
"text":"My status"
"favorited":false
% ...

Como imprimo um campo específico (indicado por -v k=text)?

um usuário
fonte
5
Erm que não é bom json parsing btw ... e os caracteres de escape nas strings ... etc Existe uma resposta python para isso no SO (uma resposta perl mesmo ...)?
martinr 23/12/2009
51
Sempre que alguém diz que "o problema X pode ser facilmente resolvido com outro idioma Y", é o código para "minha caixa de ferramentas tem apenas uma pedra para pregar unhas ... por que se preocupar com mais alguma coisa?"
precisa saber é o seguinte
22
@BryanH: exceto que às vezes o idioma Y pode estar mais equipado para resolver um problema específico X, independentemente de quantos idiomas a pessoa que sugeriu Y conhece.
Jfs # 30/13
15
Meio tarde, mas aqui vai. grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json. Isso resolve a tarefa facilmente e somente com grep e funciona perfeitamente para JSONs simples. Para JSONs complexos, você deve usar um analisador adequado.
Diosney 17/11
2
@auser, você aceitaria uma alteração de edição "with sed e awk" para "with UNIX tools" no título?
Charles Duffy

Respostas:

1127

Existem várias ferramentas projetadas especificamente com o objetivo de manipular JSON a partir da linha de comando e serão muito mais fáceis e confiáveis ​​do que com o Awk, como jq:

curl -s 'https://api.github.com/users/lambda' | jq -r '.name'

Você também pode fazer isso com ferramentas que provavelmente já estão instaladas em seu sistema, como Python usando o jsonmódulo , para evitar dependências extras, enquanto ainda beneficia de um analisador JSON adequado. O seguinte pressupõe que você deseja usar o UTF-8, no qual o JSON original deve ser codificado e é o que a maioria dos terminais modernos também usa:

Python 3:

curl -s 'https://api.github.com/users/lambda' | \
    python3 -c "import sys, json; print(json.load(sys.stdin)['name'])"

Python 2:

export PYTHONIOENCODING=utf8
curl -s 'https://api.github.com/users/lambda' | \
    python2 -c "import sys, json; print json.load(sys.stdin)['name']"

Notas históricas

Essa resposta originalmente recomendava o jsawk , que ainda deve funcionar, mas é um pouco mais complicado de usar do que jqe depende da instalação de um interpretador JavaScript independente, que é menos comum do que um interpretador Python; portanto, as respostas acima provavelmente são preferíveis:

curl -s 'https://api.github.com/users/lambda' | jsawk -a 'return this.name'

Essa resposta também originalmente usou a API do Twitter a partir da pergunta, mas essa API não funciona mais, dificultando a cópia dos exemplos a serem testados e a nova API do Twitter requer chaves da API. Por isso, mudei para o uso da API do GitHub que pode ser usado facilmente sem chaves de API. A primeira resposta para a pergunta original seria:

curl 'http://twitter.com/users/username.json' | jq -r '.text'
Brian Campbell
fonte
7
@thrau +1. O jq está disponível no repositório e é super fácil de usar, por isso é muito melhor que o jsawk. Eu testei tanto por alguns minutos, jq ganhou esta batalha
Szymon Sadlo
1
Observe que no Python 2, se você estiver canalizando a saída para outro comando, a printinstrução sempre codificará para ASCII porque você está usando o Python em um pipe. Insira PYTHONIOENCODING=<desired codec>no comando para definir uma codificação de saída diferente, adequada para o seu terminal. No Python 3, o padrão é UTF-8 nesse caso (usando a print() função ).
Martijn Pieters
1
Instale jq em OSX com bebida instalar jq
Andy Fraley
1
curl -sé equivalente a curl --silent, enquanto jq -rsignifica, jq --raw-outputie, sem aspas.
Serge Stroobandt 26/10
python -c "solicitações de importação; r = orders.get (' api.github.com/users/lambda');print r.json () [' name '];" . O mais simples!
NotTooTechy
277

Para extrair rapidamente os valores de uma chave específica, eu pessoalmente gosto de usar "grep -o", que retorna apenas a correspondência da regex. Por exemplo, para obter o campo "texto" dos tweets, algo como:

grep -Po '"text":.*?[^\\]",' tweets.json

Esse regex é mais robusto do que você imagina; por exemplo, lida bem com seqüências de caracteres com vírgulas incorporadas e aspas de escape dentro delas. Eu acho que com um pouco mais de trabalho você poderia fazer um que garantisse extrair o valor, se for atômico. (Se houver aninhamento, uma regex não poderá fazê-lo, é claro.)

E para ainda mais limpo (embora mantendo escape original do string) você pode usar algo como: | perl -pe 's/"text"://; s/^"//; s/",$//'. (Fiz isso para esta análise .)

Para todos os odiadores que insistem em usar um analisador JSON real - sim, isso é essencial para a correção, mas

  1. Para fazer uma análise realmente rápida, como contar valores para verificar erros de limpeza de dados ou obter uma idéia geral dos dados, digitar algo na linha de comando é mais rápido. Abrir um editor para escrever um script é uma distração.
  2. grep -oé uma ordem de magnitude mais rápida que a jsonbiblioteca padrão do Python , pelo menos ao fazer isso para tweets (com aproximadamente 2 KB cada). Não tenho certeza se isso é apenas porque jsoné lento (eu devo comparar com o yajl em algum momento); mas, em princípio, um regex deve ser mais rápido, pois é um estado finito e muito mais otimizável, em vez de um analisador que precisa oferecer suporte à recursão e, nesse caso, gasta muitas árvores de construção de CPU para estruturas com as quais você não se importa. (Se alguém escrevesse um transdutor de estado finito que fizesse uma análise JSON adequada (com profundidade limitada), isso seria fantástico! Enquanto isso, temos "grep -o".)

Para escrever código de manutenção, eu sempre uso uma biblioteca de análise real. Eu não tentei o jsawk , mas se funcionar bem, isso abordaria o ponto 1.

Uma última solução, mais estranha: escrevi um script que usa Python jsone extrai as chaves que você deseja, em colunas separadas por tabulação; então eu atravesso um invólucro awkque permite o acesso nomeado às colunas. Aqui: os scripts json2tsv e tsvawk . Portanto, para este exemplo, seria:

json2tsv id text < tweets.json | tsvawk '{print "tweet " $id " is: " $text}'

Essa abordagem não aborda o número 2, é mais ineficiente do que um único script Python e é um pouco frágil: força a normalização de novas linhas e guias nos valores das strings, para que seja agradável com a visão de mundo / campo delimitada por registros do awk. Mas permite que você permaneça na linha de comando, com mais correção do que grep -o.

Brendan OConnor
fonte
11
Você esqueceu os valores inteiros. grep -Po '"text":(\d*?,|.*?[^\\]",)'
Robert
3
Robert: Certo, meu regex foi escrito apenas para valores de string para esse campo. Inteiros podem ser adicionados como você diz. Se você deseja todos os tipos, precisa fazer mais e mais: booleanos, nulo. E matrizes e objetos exigem mais trabalho; somente profundidade limitada é possível, sob regexes padrão.
Brendan OConnor
9
1. jq .namefunciona na linha de comando e não requer "abrir um editor para escrever um script". 2. Não importa o quão rápido o seu regex pode produzir erradas resultados
JFS
6
e se você quer apenas os valores, pode simplesmente despertar isso. | grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
JeffCharter
34
Parece que no OSX -Pestá faltando a opção. Eu testei no OSX 10.11.5 e grep --versionfoi grep (BSD grep) 2.5.1-FreeBSD. Eu consegui trabalhar com a opção "regex estendida" no OSX. O comando de cima seria grep -Eo '"text":.*?[^\\]",' tweets.json.
Jens
174

Considerando que algumas das recomendações aqui (especialmente nos comentários) sugeriram o uso do Python, fiquei desapontado por não encontrar um exemplo.

Então, aqui está uma lista para obter um valor único de alguns dados JSON. Ele pressupõe que você está canalizando os dados (de algum lugar) e, portanto, deve ser útil em um contexto de script.

echo '{"hostname":"test","domainname":"example.com"}' | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["hostname"]'
paulkmoore
fonte
Eu aprimorei esta resposta abaixo para usar uma função bash: curl 'some_api' | getJsonVal 'key'
Joe Heyming
pythonpy( github.com/russell91/pythonpy é quase sempre uma alternativa melhor python -c, embora precise ser instalado com o pip. basta canalizar o json para py --ji -x 'x[0]["hostname"]'. Se você não quiser usar o suporte json_input embutido, ainda poderá obter aqueles importação automaticamente comopy 'json.loads(sys.stdin)[0]["hostname"]'
RussellStewart
2
Obrigado! Para uma análise JSON mais rápida e suja, envolvi-a em uma função bash: jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); print($1)"; }para que eu pudesse escrever: curl ...... | jsonq 'json.dumps([key["token"] for key in obj], indent=2)'& mais coisas assustadoras similares ... Aliás, obj[0]parece desnecessário, parece que objfunciona apenas nos casos padrão (?).
27615 akavelvel
Obrigado. Eu fiz esta JSON respeito um pouco melhor do que o de impressão:jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); sys.stdout.write(json.dumps($1))"; }
Adam K Dean
4
obj[0]causa um erro ao analisar { "port":5555 }. Funciona bem após a remoção [0].
CyberEd
134

Seguindo a liderança de MartinR e Boecko:

$ curl -s 'http://twitter.com/users/username.json' | python -mjson.tool

Isso lhe dará uma saída extremamente amigável para grep. Muito conveniente:

$ curl -s 'http://twitter.com/users/username.json' | python -mjson.tool | grep my_key
jnrg
fonte
37
Como você extrairia uma chave específica, como o OP está perguntando?
juan
2
A melhor resposta até agora, não é necessário instalar mais nada na maioria das distribuições e você pode | grep field. Obrigado!
Andrea Richiardi
7
Tudo isso faz é formatar o JSON, se não me engano. Ele não permite que o chamador selecione um campo específico da saída, como faria uma solução xpath ou algo baseado no "JSON Pointer".
Cheeso
4
Acabo com um par de valores-chave, mas não o valor em si.
28417 christopher
1
jqnormalmente não é instalado enquanto o python estiver. Além disso, uma vez que seu em Python que você pode também ir toda a maneira e analisá-lo comimport json...
CpILL
125

Você pode simplesmente baixar o jqbinário para sua plataforma e executar ( chmod +x jq):

$ curl 'https://twitter.com/users/username.json' | ./jq -r '.name'

Extrai o "name"atributo do objeto json.

jqA página inicial diz que é sedpara dados JSON.

jfs
fonte
27
Só para constar, jqé uma ferramenta incrível.
hoss
2
Acordado. Não posso comparar com o jsawk da resposta aceita, pois ainda não o usei, mas para experimentação local (onde a instalação de uma ferramenta é aceitável), recomendo o jq. Aqui está um exemplo um pouco mais extensa, o que leva cada elemento de uma matriz e sintetiza um novo objeto JSON com dados selecionados: curl -s https://api.example.com/jobs | jq '.jobs[] | {id, o: .owner.username, dateCreated, s: .status.state}'
jbyler
2
Amo isso. Muito leve e, como está no simples C antigo, pode ser compilado em qualquer lugar.
21914 Benmj
1
A prática mais: ele não precisa de bibliotecas de terceiros (enquanto jsawk faz) e é fácil de instalar (OSX: fermentação instalar jq)
lauhub
1
Essa é a resposta mais prática e fácil de implementar para o meu caso de uso. Para o sistema Ubuntu (14.04), um simples apt-get install jq adicionou a ferramenta ao meu sistema. Estou canalizando a saída JSON das respostas da AWS CLI para o jq e funciona muito bem para extrair valores para determinadas chaves aninhadas na resposta.
Brandon K
105

Usando o Node.js

Se o sistema tiver instalado, é possível usar os sinalizadores de script de -pimpressão e avaliação para extrair qualquer valor necessário.-eJSON.parse

Um exemplo simples usando a string JSON { "foo": "bar" }e retirando o valor de "foo":

$ node -pe 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
bar

Como temos acesso cate outros utilitários, podemos usá-lo para arquivos:

$ node -pe 'JSON.parse(process.argv[1]).foo' "$(cat foobar.json)"
bar

Ou qualquer outro formato, como um URL que contém JSON:

$ node -pe 'JSON.parse(process.argv[1]).name' "$(curl -s https://api.github.com/users/trevorsenior)"
Trevor Senior
JayQuerie.com
fonte
1
obrigado! mas no meu caso que está trabalhando apenas com bandeira -enode -p -e 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
Rnd_d
33
Tubos! curl -s https://api.github.com/users/trevorsenior | node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).name"
Nicerobot
4
esta é a minha solução favorita; use uma linguagem (javascript) para analisar uma estrutura de dados que é natural para ela (JSON). parece o mais correto . também - o nó provavelmente já está disponível no sistema, e você não terá que usar os binários do jq (que se parecem com outra opção correta ).
Eliran Malka
Esta é a função do script bash: # jsonv obtém o valor do objeto json para um atributo específico # primeiro parâmetro é o documento json # segundo parâmetro é o atributo cujo valor deve ser retornado get_json_attribute_value () {node -pe 'JSON.parse (process. argv [1]) [process.argv [2]]' "$ 1" "$ 2"}
Youness
6
O seguinte funciona com o Node.js 10:cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
Ilya Boyandin 15/10
100

Use o suporte JSON do Python em vez de usar o awk!

Algo assim:

curl -s http://twitter.com/users/username.json | \
    python -c "import json,sys;obj=json.load(sys.stdin);print obj['name'];"
martinr
fonte
6
Perdoe-me por tentar encontrar uma boa resposta ...: tentarei mais. O partidarismo exige mais do que escrever um script awk para se livrar dele!
MartinR
9
Por que você usa a variável obj nessa solução oneliner? É inútil e não é armazenado, afinal? Você escreve menos usando json.load(sys.stdin)['"key']"como exemplo, como: curl -sL httpbin.org/ip | python -c "import json,sys; print json.load(sys.stdin)['origin']".
M3nda
65

Você perguntou como se dar um tiro no pé e eu estou aqui para fornecer a munição:

curl -s 'http://twitter.com/users/username.json' | sed -e 's/[{}]/''/g' | awk -v RS=',"' -F: '/^text/ {print $2}'

Você poderia usar em tr -d '{}'vez de sed. Mas deixá-los de fora completamente parece ter o efeito desejado também.

Se você deseja retirar as aspas externas, canalize o resultado acima sed 's/\(^"\|"$\)//g'

Eu acho que outros soaram alarme suficiente. Estarei esperando com um telefone celular para chamar uma ambulância. Dispare quando pronto.

Pausado até novo aviso.
fonte
10
Dessa maneira, está a loucura, leia o seguinte: stackoverflow.com/questions/1732348/…
Pausado até novo aviso.
3
Eu li todas as respostas e esta funciona perfeitamente para mim sem dependências extras. 1
eth0 26/01
Era isso que eu estava procurando. A única correção - desde comando sed para remover as aspas não funcionou para mim, eu usei sed 's / "// g' em vez
AlexG
44

Usando o Bash com Python

Crie uma função bash no seu arquivo .bash_rc

function getJsonVal () { 
    python -c "import json,sys;sys.stdout.write(json.dumps(json.load(sys.stdin)$1))"; 
}

Então

$ curl 'http://twitter.com/users/username.json' | getJsonVal "['text']"
My status
$ 

Aqui está a mesma função, mas com verificação de erros.

function getJsonVal() {
   if [ \( $# -ne 1 \) -o \( -t 0 \) ]; then
       cat <<EOF
Usage: getJsonVal 'key' < /tmp/
 -- or -- 
 cat /tmp/input | getJsonVal 'key'
EOF
       return;
   fi;
   python -c "import json,sys;sys.stdout.write(json.dumps(json.load(sys.stdin)$1))";
}

Onde $ # -ne 1 garante pelo menos 1 entrada e -t 0 garante que você está redirecionando de um canal.

O bom dessa implementação é que você pode acessar valores json aninhados e obter json em troca! =)

Exemplo:

$ echo '{"foo": {"bar": "baz", "a": [1,2,3]}}' |  getJsonVal "['foo']['a'][1]"
2

Se você quiser ser realmente chique, pode imprimir os dados:

function getJsonVal () { 
    python -c "import json,sys;sys.stdout.write(json.dumps(json.load(sys.stdin)$1, sort_keys=True, indent=4))"; 
}

$ echo '{"foo": {"bar": "baz", "a": [1,2,3]}}' |  getJsonVal "['foo']"
{
    "a": [
        1, 
        2, 
        3
    ], 
    "bar": "baz"
}
Joe Heyming
fonte
Linha única sem a função bash:curl http://foo | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["environment"][0]["name"]'
Cheeso
1
sys.stdout.write()se você quer que ele funcione tanto com python 2 e 3.
Per Johansson
Eu estou pensando que ele deve mudar para system.stdout.write (obj $ 1). Dessa forma, você pode dizer: getJsonVal "['environment'] ['name']", como o exemplo de
@Cheeso
1
@Narek Nesse caso, seria algo como isto: functiongetJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
Joe Heyming
30

TickTick é um analisador JSON escrito em bash (<250 linhas de código)

Aqui está o trecho do autor de seu artigo, Imagine um mundo onde o Bash suporta JSON :

#!/bin/bash
. ticktick.sh

``  
  people = { 
    "Writers": [
      "Rod Serling",
      "Charles Beaumont",
      "Richard Matheson"
    ],  
    "Cast": {
      "Rod Serling": { "Episodes": 156 },
      "Martin Landau": { "Episodes": 2 },
      "William Shatner": { "Episodes": 2 } 
    }   
  }   
``  

function printDirectors() {
  echo "  The ``people.Directors.length()`` Directors are:"

  for director in ``people.Directors.items()``; do
    printf "    - %s\n" ${!director}
  done
}   

`` people.Directors = [ "John Brahm", "Douglas Heyes" ] ``
printDirectors

newDirector="Lamont Johnson"
`` people.Directors.push($newDirector) ``
printDirectors

echo "Shifted: "``people.Directors.shift()``
printDirectors

echo "Popped: "``people.Directors.pop()``
printDirectors
CoolAJ86
fonte
2
Como a única resposta robusta e pura aqui, isso merece mais votos.
Ed Randall
Existe alguma maneira de imprimir essa variável de pessoas em uma string json novamente? Isso seria extremamente útil
Thomas Fournet
1
Finalmente, uma resposta que não recomenda Python ou outros métodos atrozes ... Obrigado!
Akito 10/04
21

Analisando JSON com PHP CLI

Indiscutivelmente fora de tópico, mas como a precedência reina, essa pergunta permanece incompleta sem uma menção ao nosso fiel e fiel PHP, estou certo?

Usando o mesmo exemplo JSON, mas vamos atribuí-lo a uma variável para reduzir a obscuridade.

$ export JSON='{"hostname":"test","domainname":"example.com"}'

Agora, para o PHP, usando file_get_contents e o php: // stdin stream wrapper.

$ echo $JSON|php -r 'echo json_decode(file_get_contents("php://stdin"))->hostname;'

ou conforme indicado usando fgets e o fluxo já aberto na constante STDIN da CLI .

$ echo $JSON|php -r 'echo json_decode(fgets(STDIN))->hostname;'

nJoy!

nickl-
fonte
Você pode até usar em $argnvez defgets(STDIN)
IcanDivideBy0
Opa, $argntrabalha com o -E ou opção -R e somente se o conteúdo JSON está em uma linha ...
IcanDivideBy0
21

Versão nativa do Bash: também funciona bem com barras invertidas (\) e aspas (")

function parse_json()
{
    echo $1 | \
    sed -e 's/[{}]/''/g' | \
    sed -e 's/", "/'\",\"'/g' | \
    sed -e 's/" ,"/'\",\"'/g' | \
    sed -e 's/" , "/'\",\"'/g' | \
    sed -e 's/","/'\"---SEPERATOR---\"'/g' | \
    awk -F=':' -v RS='---SEPERATOR---' "\$1~/\"$2\"/ {print}" | \
    sed -e "s/\"$2\"://" | \
    tr -d "\n\t" | \
    sed -e 's/\\"/"/g' | \
    sed -e 's/\\\\/\\/g' | \
    sed -e 's/^[ \t]*//g' | \
    sed -e 's/^"//'  -e 's/"$//'
}


parse_json '{"username":"john, doe","email":"[email protected]"}' username
parse_json '{"username":"john doe","email":"[email protected]"}' email

--- outputs ---

john, doe
johh@doe.com
maikel
fonte
Isso é incrível. Mas se a string JSON contém mais de uma chave e-mail, a saída analisador vontade [email protected] "" [email protected]
rtc11
Não funciona se houver um traço no e-mail como [email protected]
alexmngn
13

Versão que usa Ruby e http://flori.github.com/json/

$ < file.json ruby -e "require 'rubygems'; require 'json'; puts JSON.pretty_generate(JSON[STDIN.read]);"

ou mais concisamente:

$ < file.json ruby -r rubygems -r json -e "puts JSON.pretty_generate(JSON[STDIN.read]);"
boecko
fonte
3
este é o meu favorito;) BTW você pode suma, com rubi -rjson para exigir a biblioteca
lucapette
Observe que a final ;não é necessária no Ruby (é usada apenas para concatenar instruções que normalmente estariam em linhas separadas em uma única linha).
Zack Morris
11

Infelizmente, a resposta mais votada que usa grepretorna a correspondência completa que não funcionou no meu cenário, mas se você souber que o formato JSON permanecerá constante, poderá usar lookbehind e lookahead para extrair apenas os valores desejados.

# echo '{"TotalPages":33,"FooBar":"he\"llo","anotherValue":100}' | grep -Po '(?<="FooBar":")(.*?)(?=",)'
he\"llo
# echo '{"TotalPages":33,"FooBar":"he\"llo","anotherValue":100}' | grep -Po '(?<="TotalPages":)(.*?)(?=,)'
33
#  echo '{"TotalPages":33,"FooBar":"he\"llo","anotherValue":100}' | grep -Po '(?<="anotherValue":)(.*?)(?=})'
100
Daniel Sokolowski
fonte
Você nunca sabe realmente a ordem dos elementos em um dicionário JSON. Eles são, por definição, desordenados. Essa é precisamente uma das razões fundamentais pelas quais lançar seu próprio analisador JSON é uma abordagem condenada.
tripleee
10

Se alguém apenas deseja extrair valores de objetos JSON simples sem a necessidade de estruturas aninhadas, é possível usar expressões regulares sem sair do bash.

Aqui está uma função que eu defini usando expressões regulares do bash com base no padrão JSON :

function json_extract() {
  local key=$1
  local json=$2

  local string_regex='"([^"\]|\\.)*"'
  local number_regex='-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?'
  local value_regex="${string_regex}|${number_regex}|true|false|null"
  local pair_regex="\"${key}\"[[:space:]]*:[[:space:]]*(${value_regex})"

  if [[ ${json} =~ ${pair_regex} ]]; then
    echo $(sed 's/^"\|"$//g' <<< "${BASH_REMATCH[1]}")
  else
    return 1
  fi
}

Advertências: objetos e matrizes não são suportados como valor, mas todos os outros tipos de valores definidos no padrão são suportados. Além disso, um par será correspondido, independentemente da profundidade do documento JSON, desde que tenha exatamente o mesmo nome da chave.

Usando o exemplo do OP:

$ json_extract text "$(curl 'http://twitter.com/users/username.json')"
My status

$ json_extract friends_count "$(curl 'http://twitter.com/users/username.json')"
245
Helder Pereira
fonte
Helder Pereira, podemos extrair valores de propriedades aninhados com essa função?
vsbehere 8/01
8

Existe uma maneira mais fácil de obter uma propriedade de uma string json. Usando um package.jsonarquivo como exemplo, tente o seguinte:

#!/usr/bin/env bash
my_val="$(json=$(<package.json) node -pe "JSON.parse(process.env.json)['version']")"

Estamos usando process.envporque isso coloca o conteúdo do arquivo no node.js como uma string, sem risco de conteúdo malicioso escapar das aspas e ser analisado como código.

Alexander Mills
fonte
O uso da concatenação de cadeias para substituir valores em uma cadeia analisada como código permite que o código node.js arbitrário seja executado, o que significa que é extremamente inseguro usar com conteúdo aleatório que você saiu da Internet. Há uma razão pela qual as maneiras seguras / práticas recomendadas de analisar o JSON no JavaScript não são apenas avaliadas.
Charles Duffy
@CharlesDuffy não tenho certeza de que sigo, mas a chamada JSON.parse deve ser mais segura, pois require()pode realmente executar código estrangeiro, JSON.parse não.
Alexander Mills
Isso é verdade se e somente se sua cadeia é realmente injetada no tempo de execução JSON de forma a ignorar o analisador. Não vejo o código aqui fazendo isso de forma confiável. Puxe-o de uma variável de ambiente e passe-o para JSON.parse()e sim, você é inequivocamente seguro ... mas aqui, o tempo de execução JSON está recebendo o conteúdo (não confiável) em banda com o código (confiável).
Charles Duffy
... da mesma forma, se você tiver seu código lido o JSON do arquivo como uma string e passado essa string para JSON.parse(), também estará seguro, mas isso também não está acontecendo aqui.
Charles Duffy
1
... ahh, diabos, pode muito bem entrar no "como" imediatamente. O problema é que você está substituindo a variável do shell, para a qual pretende passar JSON.parse(), no código . Você está assumindo que colocar backticks literais manterá o conteúdo literal, mas isso é uma suposição completamente insegura, porque backticks literais podem existir no conteúdo do arquivo (e, portanto, na variável) e, portanto, podem terminar a citação e entrar em um contexto não citado, onde valores são executados como código.
Charles Duffy
7

Agora que o Powershell é multiplataforma, pensei em abrir caminho, pois acho que é bastante intuitivo e extremamente simples.

curl -s 'https://api.github.com/users/lambda' | ConvertFrom-Json 

ConvertFrom-Json converte o JSON em um objeto personalizado do Powershell, para que você possa trabalhar facilmente com as propriedades desse ponto em diante. Se você quiser apenas a propriedade 'id', por exemplo, basta fazer o seguinte:

curl -s 'https://api.github.com/users/lambda' | ConvertFrom-Json | select -ExpandProperty id

Se você quiser invocar a coisa toda a partir do Bash, precisará chamar assim:

powershell 'curl -s "https://api.github.com/users/lambda" | ConvertFrom-Json'

É claro que existe uma maneira pura do Powershell de fazer isso sem enrolar, o que seria:

Invoke-WebRequest 'https://api.github.com/users/lambda' | select -ExpandProperty Content | ConvertFrom-Json

Por fim, há também 'ConvertTo-Json', que converte um objeto personalizado em JSON com a mesma facilidade. Aqui está um exemplo:

(New-Object PsObject -Property @{ Name = "Tester"; SomeList = @('one','two','three')}) | ConvertTo-Json

O que produziria JSON legal como este:

{
"Name":  "Tester",
"SomeList":  [
                 "one",
                 "two",
                 "three"
             ]

}

É certo que usar um shell do Windows no Unix é um pouco sacrílego, mas o Powershell é realmente bom em algumas coisas, e a análise de JSON e XML são algumas delas. Esta é a página do GitHub para a versão multiplataforma https://github.com/PowerShell/PowerShell

user2233949
fonte
votado porque você está promovendo a nova estratégia da Microsoft para abrir suas ferramentas de código-fonte e incorporar ferramentas estrangeiras de código-fonte aberto. É uma coisa boa para o nosso mundo.
22417 Alex
Eu não gostava do PowerShell, mas devo admitir que o manuseio do JSON como objetos é bastante agradável.
MartinThé
6

Alguém que também tenha arquivos xml, pode querer dar uma olhada no meu Xidel . É um processador JSONiq livre de dependência e cli . (ou seja, ele também suporta XQuery para processamento xml ou json)

O exemplo na pergunta seria:

 xidel -e 'json("http://twitter.com/users/username.json")("name")'

Ou com minha própria sintaxe de extensão não padrão:

 xidel -e 'json("http://twitter.com/users/username.json").name'
BeniBela
fonte
1
Ou mais simples hoje em dia: xidel -s https://api.github.com/users/lambda -e 'name'(ou -e '$json/name', ou -e '($json).name').
Reino
6

Não posso usar nenhuma das respostas aqui. Sem jq disponível, sem matrizes de shell, sem declarar, sem grep -P, sem lookbehind e lookahead, sem Python, sem Perl, sem Ruby, não - nem mesmo Bash ... As respostas restantes simplesmente não funcionam bem. O JavaScript parecia familiar, mas a lata diz Nescaffe - então também não é possível :) Mesmo se disponíveis, para minha simples necessidade - eles seriam um exagero e lentos.

No entanto, é extremamente importante para mim obter muitas variáveis ​​da resposta formatada em json do meu modem. Estou fazendo isso em um sh com o BusyBox muito aparado nos meus roteadores! Não há problemas ao usar o awk sozinho: basta definir delimitadores e ler os dados. Para uma única variável, isso é tudo!

awk 'BEGIN { FS="\""; RS="," }; { if ($2 == "login") {print $4} }' test.json

Lembra que não tenho matrizes? Eu tive que atribuir dentro dos dados analisados ​​do awk às 11 variáveis ​​necessárias em um script de shell. Onde quer que eu olhasse, isso era uma missão impossível. Não tem problema com isso também.

Minha solução é simples. Este código irá: 1) analisar o arquivo .json da pergunta (na verdade, peguei emprestada uma amostra de dados de trabalho da resposta mais votada) e seleciono os dados citados, além de 2) criar variáveis ​​de shell a partir do awk, designando shell nomeado gratuito nomes de variáveis.

eval $( curl -s 'https://api.github.com/users/lambda' | 
awk ' BEGIN { FS="\""; RS="," };
{
    if ($2 == "login") { print "Login=\""$4"\"" }
    if ($2 == "name") { print "Name=\""$4"\"" }
    if ($2 == "updated_at") { print "Updated=\""$4"\"" }
}' )
echo "$Login, $Name, $Updated"

Sem problemas com espaços em branco no interior. No meu uso, o mesmo comando analisa uma saída longa de linha única. Como o eval é usado, esta solução é adequada apenas para dados confiáveis. É simples adaptá-lo para coletar dados não citados. Para um grande número de variáveis, o ganho marginal de velocidade pode ser alcançado usando else if. A falta de matriz obviamente significa: não há vários registros sem mexer extra. Mas onde as matrizes estão disponíveis, adaptar esta solução é uma tarefa simples.

@maikel sed resposta quase funciona (mas não posso comentar). Para meus dados bem formatados - funciona. Não muito com o exemplo usado aqui (aspas ausentes o impedem). É complicado e difícil de modificar. Além disso, não gosto de fazer 11 chamadas para extrair 11 variáveis. Por quê? Cronometrei 100 loops para extrair 9 variáveis: a função sed levou 48,99 segundos e minha solução levou 0,91 segundos! Não é justo? Fazendo apenas uma extração única de 9 variáveis: 0,51 vs. 0,02 seg.

Pila
fonte
5

Você pode tentar algo assim -

curl -s 'http://twitter.com/users/jaypalsingh.json' | 
awk -F=":" -v RS="," '$1~/"text"/ {print}'
jaypal singh
fonte
5

Você pode usar jshon:

curl 'http://twitter.com/users/username.json' | jshon -e text
kev
fonte
O site diz: "Duas vezes mais rápido, 1/6 da memória" ... e depois: "Jshon analisa, lê e cria JSON. Ele foi projetado para ser o mais utilizável possível dentro do shell e substitui analisadores adhoc frágeis feitos de grep / sed / awk, bem como analisadores de linha única pesados ​​feitos de perl / python. "
Roger
este é listado como a solução recomendada para analisar JSON em Bash
qodeninja
Qual é a maneira mais fácil de se livrar das aspas ao redor do resultado?
gMale
4

aqui está uma maneira de fazer isso com o awk

curl -sL 'http://twitter.com/users/username.json' | awk -F"," -v k="text" '{
    gsub(/{|}/,"")
    for(i=1;i<=NF;i++){
        if ( $i ~ k ){
            print $i
        }
    }
}'
ghostdog74
fonte
4

Para uma análise JSON mais complexa, sugiro usar o módulo python jsonpath (de Stefan Goessner) -

  1. Instale-o -

sudo easy_install -U jsonpath

  1. Use-o -

Exemplo file.json (de http://goessner.net/articles/JsonPath ) -

{ "store": {
    "book": [ 
      { "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      { "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      { "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      { "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}

Analise-o (extraia todos os títulos dos livros com preço <10) -

$ cat file.json | python -c "import sys, json, jsonpath; print '\n'.join(jsonpath.jsonpath(json.load(sys.stdin), 'store.book[?(@.price < 10)].title'))"

Saída -

Sayings of the Century
Moby Dick

NOTA: A linha de comando acima não inclui a verificação de erros. Para obter uma solução completa com verificação de erro, você deve criar um pequeno script python e agrupar o código com try-except.

shlomosh
fonte
idioma bonito. Eu nem sei Python, mas esta parece ser uma solução poderosa
Sridhar Sarnobat
Eu estava com alguns problemas jsonpathpara instalar e instalar jsonpath_rw, então, aqui está algo semelhante que você pode tentar se o acima não funcionar: 1) /usr/bin/python -m pip install jsonpath-rw2) cat ~/trash/file.json | /usr/bin/python -c "from jsonpath_rw import jsonpath, parse; import sys,json; jsonpath_expr = parse('store.book[0]'); out = [match.value for match in jsonpath_expr.find(json.load(sys.stdin))]; print out;"(usei o caminho completo para o binário python porque estava tendo alguns problemas com vários pythons instalado).
Sridhar Sarnobat
4

Se você tem php :

php -r 'var_export(json_decode(`curl http://twitter.com/users/username.json`, 1));'

Por exemplo:
temos um recurso que fornece ao json códigos iso de países: http://country.io/iso3.json e podemos vê-lo facilmente em um shell com curl:

curl http://country.io/iso3.json

mas parece não muito conveniente e não legível, melhor analise json e veja a estrutura legível:

php -r 'var_export(json_decode(`curl http://country.io/iso3.json`, 1));'

Este código imprimirá algo como:

array (
  'BD' => 'BGD',
  'BE' => 'BEL',
  'BF' => 'BFA',
  'BG' => 'BGR',
  'BA' => 'BIH',
  'BB' => 'BRB',
  'WF' => 'WLF',
  'BL' => 'BLM',
  ...

se você tiver matrizes aninhadas, essa saída ficará muito melhor ...

Espero que isso seja útil ...

V. Kovpak
fonte
4

Também existe uma ferramenta de processamento JSON CLI muito simples, mas poderosa, fx - https://github.com/antonmedv/fx

Exemplo de formatação JSON no terminal Bash

Exemplos

Use a função anônima:

$ echo '{"key": "value"}' | fx "x => x.key"
value

Se você não passar a função anônima param => ..., o código será automaticamente transformado em função anônima. E você pode obter acesso ao JSON por esta palavra-chave:

$ echo '[1,2,3]' | fx "this.map(x => x * 2)"
[2, 4, 6]

Ou apenas use a sintaxe do ponto:

$ echo '{"items": {"one": 1}}' | fx .items.one
1

Você pode transmitir qualquer número de funções anônimas para reduzir o JSON:

$ echo '{"items": ["one", "two"]}' | fx "this.items" "this[1]"
two

Você pode atualizar o JSON existente usando o operador spread:

$ echo '{"count": 0}' | fx "{...this, count: 1}"
{"count": 1}

Apenas JavaScript simples . Não precisa aprender nova sintaxe.


ATUALIZAÇÃO 10/11/2018

fxagora tem o modo interativo ( ! )

https://github.com/antonmedv/fx

Anton Medvedev
fonte
7
Se você está promovendo sua própria criação, precisa ser explícito. Consulte Como não ser um spammer.
Tripleee
4

Este é mais um bashe pythonresposta híbrido. Postei esta resposta porque queria processar uma saída JSON mais complexa, mas reduzindo a complexidade do meu aplicativo bash. Quero abrir o seguinte objeto JSON em http://www.arcgis.com/sharing/rest/info?f=json em bash:

{
  "owningSystemUrl": "http://www.arcgis.com",
  "authInfo": {
    "tokenServicesUrl": "https://www.arcgis.com/sharing/rest/generateToken",
    "isTokenBasedSecurity": true
  }
}

No exemplo a seguir, criei minha própria implementação jqe unquotealavancagem python. Você notará que, depois de importar o objeto python de jsonpara um dicionário python, podemos usar a sintaxe python para navegar no dicionário. Para navegar acima, a sintaxe é:

  • data
  • data[ "authInfo" ]
  • data[ "authInfo" ][ "tokenServicesUrl" ]

Usando magia no bash, omitimos datae fornecemos apenas o texto python à direita dos dados, ou seja,

  • jq
  • jq '[ "authInfo" ]'
  • jq '[ "authInfo" ][ "tokenServicesUrl" ]'

Observe que, sem parâmetros, jqatua como um pré-identificador JSON. Com os parâmetros, podemos usar a sintaxe python para extrair o que quisermos do dicionário, incluindo a navegação em subdicionários e elementos de matriz.

Aqui está um exemplo de trabalho que demonstra o acima:

jq_py() {
cat <<EOF
import json, sys
data = json.load( sys.stdin )
print( json.dumps( data$1, indent = 4 ) )
EOF
}

jq() {
  python -c "$( jq_py "$1" )"
}

unquote_py() {
cat <<EOF
import json,sys
print( json.load( sys.stdin ) )
EOF
}

unquote() {
  python -c "$( unquote_py )"
}

curl http://www.arcgis.com/sharing/rest/info?f=json | tee arcgis.json
# {"owningSystemUrl":"https://www.arcgis.com","authInfo":{"tokenServicesUrl":"https://www.arcgis.com/sharing/rest/generateToken","isTokenBasedSecurity":true}}

cat arcgis.json | jq
# {
#     "owningSystemUrl": "https://www.arcgis.com",
#     "authInfo": {
#         "tokenServicesUrl": "https://www.arcgis.com/sharing/rest/generateToken",
#         "isTokenBasedSecurity": true
#     }
# }

cat arcgis.json | jq '[ "authInfo" ]'
# {
#     "tokenServicesUrl": "https://www.arcgis.com/sharing/rest/generateToken",
#     "isTokenBasedSecurity": true
# }

cat arcgis.json | jq '[ "authInfo" ][ "tokenServicesUrl" ]'
# "https://www.arcgis.com/sharing/rest/generateToken"

cat arcgis.json | jq '[ "authInfo" ][ "tokenServicesUrl" ]' | unquote
# https://www.arcgis.com/sharing/rest/generateToken
Stephen Quan
fonte
3

Eu fiz isso, "analisando" uma resposta json para um valor específico, da seguinte maneira:

curl $url | grep $var | awk '{print $2}' | sed s/\"//g 

Claramente, $ url aqui seria o Twitter, e $ var seria "texto" para obter a resposta para esse var.

Realmente, acho que a única coisa que deixei de fora do OP é grep para a linha com a variável específica que ele procura. Awk pega o segundo item da linha e, com sed, retiro as aspas.

Alguém mais esperto do que eu provavelmente poderia pensar com awk ou grep.

Agora, você pode fazer tudo com apenas sed:

curl $url | sed '/text/!d' | sed s/\"text\"://g | sed s/\"//g | sed s/\ //g

assim, sem awk, sem grep ... Não sei por que não pensei nisso antes. Hummm ...

tonybaldwin
fonte
Na verdade, com o sed você pode fazer
tonybaldwin
1
Os oleodutos grep | awk | sede sed | sed | sedsão antipadrões de desperdício. Seu último exemplo pode ser facilmente reescrito, curl "$url" | sed '/text/!d;s/\"text\"://g;s/\"//g;s/\ //g'mas, como outros já apontaram, é uma abordagem propensa a erros e quebradiça, que não deve ser recomendada em primeiro lugar.
Tripleee
Eu tive que usar grep -oPz 'name \ ": \" * \.? "' Curloutput | sed 's / nome \": / \ n / g'
Ferroao
3

A análise do JSON é dolorosa em um script de shell. Com uma linguagem mais apropriada, crie uma ferramenta que extraia atributos JSON de maneira consistente com as convenções de script de shell. Você pode usar sua nova ferramenta para resolver o problema imediato de script de shell e adicioná-lo ao seu kit para situações futuras.

Por exemplo, considere uma ferramenta jsonlookup tal que, se eu disser jsonlookup access token id, retornará o ID do atributo definido no token de atributo definido no acesso ao atributo do stdin, que é presumivelmente dados JSON. Se o atributo não existir, a ferramenta não retornará nada (status de saída 1). Se a análise falhar, saia do status 2 e de uma mensagem para stderr. Se a pesquisa for bem-sucedida, a ferramenta imprimirá o valor do atributo.

Após criar uma ferramenta unix com a finalidade precisa de extrair valores JSON, você pode usá-la facilmente em scripts de shell:

access_token=$(curl <some horrible crap> | jsonlookup access token id)

Qualquer idioma serve para a implementação do jsonlookup . Aqui está uma versão python bastante concisa:

#!/usr/bin/python                                                               

import sys
import json

try: rep = json.loads(sys.stdin.read())
except:
    sys.stderr.write(sys.argv[0] + ": unable to parse JSON from stdin\n")
    sys.exit(2)
for key in sys.argv[1:]:
    if key not in rep:
        sys.exit(1)
    rep = rep[key]
print rep
mcnabicus
fonte
3

Uma linha dupla que usa python. Funciona particularmente bem se você estiver gravando um único arquivo .sh e não quiser depender de outro arquivo .py. Ele também aproveita o uso de tubos |. echo "{\"field\": \"value\"}"pode ser substituído por qualquer coisa que imprima um json no stdout.

echo "{\"field\": \"value\"}" | python -c 'import sys, json
print(json.load(sys.stdin)["field"])'
Adam Kurkiewicz
fonte
A questão não estava procurando uma solução Python. Veja os comentários também.
Andrew Barber
3

Esta é uma boa base de dados para pythonpy :

curl 'http://twitter.com/users/username.json' | py 'json.load(sys.stdin)["name"]'
RussellStewart
fonte
Ainda mais curto, o módulo python -c aqui :) é legal.
M3nda