Analizando JSON con herramientas Unix

880

Estoy tratando de analizar JSON devuelto por una solicitud curl, así:

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

Lo anterior divide el JSON en campos, por ejemplo:

% ...
"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
% ...

¿Cómo imprimo un campo específico (indicado por el -v k=text)?

un usuario
fuente
55
Erm, eso no es bueno json analizando por cierto ... ¿qué pasa con los caracteres de escape en cadenas ... etc. ¿Hay una respuesta de Python a esto en SO (una respuesta perl incluso ...)?
martinr
51
Cada vez que alguien dice "el problema X se puede resolver fácilmente con otro lenguaje Y", ese es el código para "mi caja de herramientas solo tiene una piedra para clavar clavos ... ¿por qué molestarse con algo más?"
BryanH
22
@BryanH: excepto que a veces el lenguaje Y puede estar más equipado para resolver un problema particular X independientemente de cuántos idiomas sepa la persona que sugirió Y.
jfs
15
Un poco tarde, pero aquí va. grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json. Esto resuelve la tarea fácilmente y solo con grep y funciona perfectamente para JSON simples. Para los JSON complejos, debe usar un analizador adecuado.
diosney
2
@auser, ¿estaría de acuerdo con que una edición cambie "con sed y awk" a "con herramientas UNIX" en el título?
Charles Duffy

Respuestas:

1128

Hay una serie de herramientas diseñadas específicamente para manipular JSON desde la línea de comandos, y será mucho más fácil y más confiable que hacerlo con Awk, como jq:

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

También puede hacer esto con herramientas que probablemente ya estén instaladas en su sistema, como Python que usa el jsonmódulo , y así evitar cualquier dependencia adicional, mientras sigue teniendo el beneficio de un analizador JSON adecuado. Lo siguiente asume que desea usar UTF-8, en el que debe codificarse el JSON original y es lo que usan también los terminales más modernos:

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

Esta respuesta originalmente recomendaba jsawk , que aún debería funcionar, pero es un poco más engorroso de usar que jq, y depende de la instalación de un intérprete de JavaScript independiente que es menos común que un intérprete de Python, por lo que las respuestas anteriores probablemente sean preferibles:

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

Esta respuesta también usó originalmente la API de Twitter de la pregunta, pero esa API ya no funciona, lo que dificulta la copia de los ejemplos para probar, y la nueva API de Twitter requiere claves de API, así que he cambiado a usar la API de GitHub que se puede usar fácilmente sin claves API. La primera respuesta para la pregunta original sería:

curl 'http://twitter.com/users/username.json' | jq -r '.text'
Brian Campbell
fuente
77
@thrau +1. jq está disponible en el repositorio y es muy fácil de usar, por lo que es mucho mejor que jsawk.
Probé
1
Tenga en cuenta que en Python 2, si está canalizando la salida a otro comando, la printinstrucción siempre codificará a ASCII porque está utilizando Python en una tubería. Inserte PYTHONIOENCODING=<desired codec>en el comando para establecer una codificación de salida diferente, adecuada para su terminal. En Python 3, el valor predeterminado es UTF-8 en este caso (usando la print() función ).
Martijn Pieters
1
Instale jq en OSX con brew install jq
Andy Fraley
1
curl -ses equivalente a curl --silent, mientras que jq -rsignifica, jq --raw-outputes decir, sin comillas.
Serge Stroobandt
python -c "solicitudes de importación; r = solicitudes.get (' api.github.com/users/lambda');print r.json () [' nombre '];" . El mas simple!
NotTooTechy
277

Para extraer rápidamente los valores de una clave en particular, personalmente me gusta usar "grep -o", que solo devuelve la coincidencia de expresiones regulares. Por ejemplo, para obtener el campo "texto" de los tweets, algo como:

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

Esta expresión regular es más robusta de lo que piensas; por ejemplo, trata bien con cadenas que tienen comas incrustadas y comillas escapadas dentro de ellas. Creo que con un poco más de trabajo podría hacer uno que realmente garantice extraer el valor, si es atómico. (Si tiene anidamiento, entonces una expresión regular no puede hacerlo, por supuesto).

Y más limpia (pero manteniendo el escape original de la cadena) se puede usar algo como: | perl -pe 's/"text"://; s/^"//; s/",$//'. (Hice esto para este análisis ).

Para todos los que odian que insisten en que debes usar un analizador JSON real, sí, eso es esencial para la corrección, pero

  1. Para hacer un análisis realmente rápido, como contar valores para verificar errores de limpieza de datos u obtener una idea general de los datos, golpear algo en la línea de comando es más rápido. Abrir un editor para escribir un guión es una distracción.
  2. grep -oes un orden de magnitud más rápido que la jsonbiblioteca estándar de Python , al menos al hacer esto para tweets (que son ~ 2 KB cada uno). No estoy seguro de si esto es solo porque jsones lento (debería compararme con yajl en algún momento); pero, en principio, una expresión regular debería ser más rápida ya que es un estado finito y mucho más optimizable, en lugar de un analizador que debe admitir la recursividad, y en este caso, gasta muchos árboles de CPU para estructuras que no le importan. (Si alguien escribiera un transductor de estado finito que hiciera un análisis JSON adecuado (limitado en profundidad), ¡sería fantástico! Mientras tanto tenemos "grep -o").

Para escribir código mantenible, siempre uso una biblioteca de análisis real. No he probado jsawk , pero si funciona bien, eso abordaría el punto # 1.

Una última solución, más alocada: escribí un script que usa Python jsony extrae las claves que desea, en columnas separadas por tabuladores; luego canalizo a través de un contenedor awkque permite el acceso con nombre a las columnas. Aquí: los scripts json2tsv y tsvawk . Entonces para este ejemplo sería:

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

Este enfoque no aborda el n. ° 2, es más ineficiente que un solo script de Python, y es un poco frágil: obliga a la normalización de nuevas líneas y pestañas en los valores de cadena, para jugar bien con el campo de awk / vista delimitada por registros del mundo. Pero te permite permanecer en la línea de comando, con más exactitud que grep -o.

Brendan OConnor
fuente
11
Te olvidaste de los valores enteros. grep -Po '"text":(\d*?,|.*?[^\\]",)'
Robert
3
Robert: Correcto, mi expresión regular se escribió solo para valores de cadena para ese campo. Se pueden agregar enteros como usted dice. Si desea todos los tipos, debe hacer más y más: booleanos, nulos. Y las matrices y los objetos requieren más trabajo; solo es posible con profundidad limitada, bajo expresiones regulares.
Brendan OConnor
99
1. jq .namefunciona en la línea de comandos y no requiere "abrir un editor para escribir un script". 2. No importa qué tan rápido su expresión regular pueda producir resultados incorrectos
jfs
66
y si solo quieres los valores, puedes arrojarle un awk. | grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
JeffCharter
34
Parece que en OSX -Pfalta la opción. Probé en OSX 10.11.5 y grep --versionfue grep (BSD grep) 2.5.1-FreeBSD. Lo conseguí trabajando con la opción "regex extendido" en OSX. El comando desde arriba sería grep -Eo '"text":.*?[^\\]",' tweets.json.
Jens
174

Sobre la base de que algunas de las recomendaciones aquí (especialmente en los comentarios) sugirieron el uso de Python, me decepcionó no encontrar un ejemplo.

Entonces, aquí hay una línea para obtener un valor único de algunos datos JSON. Se supone que está canalizando los datos (desde algún lugar) y, por lo tanto, debería ser útil en un contexto de secuencias de comandos.

echo '{"hostname":"test","domainname":"example.com"}' | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["hostname"]'
paulkmoore
fuente
He mejorado esta respuesta a continuación para usar una función bash: curl 'some_api' | getJsonVal 'key'
Joe Heyming
pythonpy( github.com/russell91/pythonpy es casi siempre una mejor alternativa para python -c, aunque tiene que instalarse con pip. simplemente conecte el json a py --ji -x 'x[0]["hostname"]'. Si no desea utilizar el soporte integrado json_input, aún puede obtener los importan automáticamente comopy 'json.loads(sys.stdin)[0]["hostname"]'
RussellStewart
2
¡Gracias! Para un análisis JSON más rápido y sucio, lo he incluido en una función bash: jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); print($1)"; }para poder escribir: curl ...... | jsonq 'json.dumps([key["token"] for key in obj], indent=2)'y más cosas similares de miedo ... Por cierto, obj[0]parece innecesario, parece que objfunciona bien en los casos predeterminados (?).
akavel
Gracias. Hice este respecto JSON un poco mejor que imprimir:jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); sys.stdout.write(json.dumps($1))"; }
Adam K Dean
44
obj[0]provoca un error al analizar { "port":5555 }. Funciona bien después de quitar [0].
CyberEd
134

Siguiendo el ejemplo de MartinR y Boecko:

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

Eso le dará una salida extremadamente amigable con grep. Muy conveniente:

$ curl -s 'http://twitter.com/users/username.json' | python -mjson.tool | grep my_key
jnrg
fuente
37
¿Cómo extraería una clave específica, como pregunta OP?
juan
2
La mejor respuesta hasta ahora, en mi humilde opinión, no es necesario instalar nada más en la mayoría de las distribuciones y puede hacerlo | grep field. ¡Gracias!
Andrea Richiardi
77
Todo lo que hace es formatear el JSON, si no me equivoco. No permite que la persona que llama seleccione un campo particular de la salida, como lo haría con una solución xpath, o algo basado en "Puntero JSON".
Cheeso
44
Simplemente termino con un par de valores clave, pero no el valor en sí mismo.
Christopher
1
jqnormalmente no se instala mientras que python sí lo está. Además, una vez que esté en Python, también podría ir todo el camino y analizarloimport json...
CpILL
125

Simplemente puede descargar el jqbinario para su plataforma y ejecutar ( chmod +x jq):

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

Extrae "name"atributo del objeto json.

jqLa página de inicio dice que es como sedpara los datos JSON.

jfs
fuente
27
Solo para que conste, jqes una herramienta increíble.
hoss
2
Convenido. No puedo comparar con jsawk de la respuesta aceptada, ya que no lo he usado, pero para la experimentación local (donde la instalación de una herramienta es aceptable) recomiendo jq. Aquí hay un ejemplo un poco más extenso, que toma cada elemento de una matriz y sintetiza un nuevo objeto JSON con datos seleccionados: curl -s https://api.example.com/jobs | jq '.jobs[] | {id, o: .owner.username, dateCreated, s: .status.state}'
jbyler
2
Me gusta esto. Muy ligero, y dado que está en la antigua C simple, se puede compilar en cualquier lugar.
Benmj
1
La única práctica más: que no necesita bibliotecas de terceros (mientras jsawk hace) y es fácil de instalar (OSX: cerveza instalar JQ)
lauhub
1
Esta es la respuesta más práctica y fácil de implementar para mi caso de uso. Para el sistema Ubuntu (14.04), un simple apt-get install jq agregó la herramienta a mi sistema. Estoy canalizando la salida JSON de las respuestas de AWS CLI a jq y funciona muy bien para extraer valores a ciertas claves anidadas en la respuesta.
Brandon K
105

Usando Node.js

Si el sistema tiene instalado, es posible usar las marcas de script -pprint y -eevaulate con JSON.parsepara extraer cualquier valor que sea necesario.

Un ejemplo simple usando la cadena JSON { "foo": "bar" }y extrayendo el valor de "foo":

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

Debido a que tenemos acceso caty otras utilidades, podemos usar esto para archivos:

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

O cualquier otro formato, como una URL que contiene JSON:

$ node -pe 'JSON.parse(process.argv[1]).name' "$(curl -s https://api.github.com/users/trevorsenior)"
Trevor Senior
JayQuerie.com
fuente
1
¡Gracias! pero en mi caso solo funciona con -e flagnode -p -e 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
Rnd_d
33
¡Tubería! curl -s https://api.github.com/users/trevorsenior | node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).name"
nicerobot
44
Esta es mi solución favorita; use un lenguaje (javascript) para analizar una estructura de datos que sea natural (JSON). Parece lo más correcto . Además, el nodo probablemente ya esté disponible en el sistema y no tendrá que manipular los binarios de jq (que parece otra opción correcta ).
Eliran Malka
Esta es la función de script bash: # jsonv obtiene el valor del objeto json para un atributo específico # el primer parámetro es el documento json # el segundo parámetro es el atributo cuyo valor debe devolverse get_json_attribute_value () {node -pe 'JSON.parse (proceso. argv [1]) [process.argv [2]] '"$ 1" "$ 2"}
Youness
66
Lo siguiente funciona con Node.js 10:cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
Ilya Boyandin
100

Use el soporte JSON de Python en lugar de usar awk!

Algo como esto:

curl -s http://twitter.com/users/username.json | \
    python -c "import json,sys;obj=json.load(sys.stdin);print obj['name'];"
martinr
fuente
66
Disculpe por intentar dar una buena respuesta ...: me esforzaré más. ¡El partidismo requiere más que escribir un guión awk para sacudirlo!
martinr
99
¿Por qué utiliza la variable obj en esa solución de línea de pedido? ¿Es inútil y no se almacena de todos modos? Se escribe menos usando json.load(sys.stdin)['"key']"como ejemplo como: curl -sL httpbin.org/ip | python -c "import json,sys; print json.load(sys.stdin)['origin']".
m3nda
65

Has preguntado cómo dispararte en el pie y estoy aquí para proporcionarte la munición:

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

Podrías usar en tr -d '{}'lugar de sed. Pero dejarlos completamente fuera parece tener también el efecto deseado.

Si desea quitar las comillas externas, canalice el resultado de lo anterior a través de sed 's/\(^"\|"$\)//g'

Creo que otros han sonado alarmados. Estaré esperando con un teléfono celular para llamar a una ambulancia. Fuego cuando esté listo.

Pausado hasta nuevo aviso.
fuente
10
De esta manera se encuentra la locura, lee esto: stackoverflow.com/questions/1732348/…
Pausado hasta nuevo aviso.
3
He leído todas las respuestas y esta funciona perfectamente para mí sin ninguna dependencia adicional. +1
eth0
Eso es lo que estaba buscando. La única corrección: el comando sed proporcionado para eliminar las comillas no funcionó para mí, he usado sed 's / "// g' en su lugar
AlexG
44

Usando Bash con Python

Cree una función bash en su archivo .bash_rc

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

Entonces

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

Aquí está la misma función, pero con comprobación de errores.

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))";
}

Donde $ # -ne 1 asegura al menos 1 entrada, y -t 0 asegura que estás redirigiendo desde una tubería.

Lo bueno de esta implementación es que puede acceder a valores json anidados y obtener json a cambio. =)

Ejemplo:

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

Si quieres ser realmente elegante, puedes imprimir los datos:

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
fuente
One-liner sin la función bash:curl http://foo | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["environment"][0]["name"]'
Cheeso
1
sys.stdout.write()si quieres que funcione con python 2 y 3.
Por Johansson
Estoy pensando que debería cambiar a system.stdout.write (obj $ 1). De esa manera puedes decir: getJsonVal "['' ambiente '] [' nombre ']", como el ejemplo de
@Cheeso
1
@Narek En ese caso, se vería así: funcióngetJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
Joe Heyming
30

TickTick es un analizador JSON escrito en bash (<250 líneas de código)

Aquí está el fragmento del autor de su artículo, Imagine un mundo en el que Bash admite 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
fuente
2
Como la única respuesta robusta de bash puro aquí, esto merece más votos a favor.
Ed Randall
¿Hay alguna forma de imprimir esta variable de personas en una cadena json nuevamente? Eso sería extremadamente útil
Thomas Fournet
1
Finalmente una respuesta que no recomienda Python u otros métodos atroces ... ¡Gracias!
Akito
21

Analizando JSON con PHP CLI

Posiblemente fuera de tema, pero dado que la precedencia reina, esta pregunta permanece incompleta sin una mención de nuestro PHP confiable y fiel, ¿estoy en lo cierto?

Usando el mismo ejemplo JSON pero vamos a asignarlo a una variable para reducir la oscuridad.

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

Ahora por bondad de PHP, usando file_get_contents y el envoltorio de flujo php: // stdin .

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

o como se señaló utilizando fgets y la secuencia ya abierta en CLI STDIN constante .

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

nJoy!

nickl-
fuente
Incluso puede usar en $argnlugar defgets(STDIN)
IcanDivideBy0
Vaya, $argnfunciona con el indicador -E o -R y solo si el contenido JSON está en una línea ...
IcanDivideBy0
21

Versión Native Bash: también funciona bien con barras invertidas (\) y comillas (")

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
fuente
Esto es asombroso Pero si la cadena JSON contiene más de una clave de correo electrónico, el analizador generará [email protected] "" [email protected]
rtc11
No funciona si hay un guión en el correo electrónico como [email protected]
alexmngn
13

Versión que usa Ruby y http://flori.github.com/json/

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

o más concisamente:

$ < file.json ruby -r rubygems -r json -e "puts JSON.pretty_generate(JSON[STDIN.read]);"
boecko
fuente
3
este es mi favorito;) Por cierto, puedes acortarlo con ruby ​​-rjson para requerir la biblioteca
lucapette
Tenga en cuenta que el final ;no se requiere en Ruby (solo se usa para concatenar declaraciones que normalmente estarían en líneas separadas en una sola línea).
Zack Morris
11

Por desgracia, la respuesta más votadas que utiliza grepdevuelve el total de los partidos que no funcionaba en mi escenario, pero si se conoce el formato JSON se mantendrá constante puede utilizar de búsqueda hacia atrás y la búsqueda hacia delante para extraer sólo los valores deseados.

# 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
fuente
Usted no realmente sabe el orden de los elementos en un diccionario JSON. Son, por definición, desordenados. Esta es precisamente una de las razones fundamentales por las que rodar su propio analizador JSON es un enfoque condenado.
tripleee
10

Si alguien solo quiere extraer valores de objetos JSON simples sin la necesidad de estructuras anidadas, es posible usar expresiones regulares sin siquiera salir del bash.

Aquí hay una función que definí usando expresiones regulares bash basadas en el estándar 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
}

Advertencias: los objetos y las matrices no se admiten como valor, pero se admiten todos los demás tipos de valores definidos en el estándar. Además, se emparejará un par, sin importar cuán profundo esté en el documento JSON, siempre que tenga exactamente el mismo nombre de clave.

Usando el ejemplo de 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
fuente
Helder Pereira, ¿podemos extraer valores de propiedad anidados con esta función?
vsbehere
8

Hay una manera más fácil de obtener una propiedad de una cadena json. Usando un package.jsonarchivo como ejemplo, intente esto:

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

Lo estamos usando process.envporque esto lleva el contenido del archivo a node.js como una cadena sin riesgo de que el contenido malicioso escape de su cita y sea analizado como código.

Alexander Mills
fuente
El uso de la concatenación de cadenas para sustituir los valores en una cadena analizada como código permite ejecutar código arbitrario de node.js, lo que significa que es extremadamente inseguro usarlo con contenido aleatorio que obtuvo de Internet. Hay una razón segura / las mejores prácticas para analizar JSON en JavaScript no solo evaluarlo.
Charles Duffy
@CharlesDuffy no estoy seguro de seguirlo, pero la llamada JSON.parse debería ser más segura, ya require()que en realidad puede ejecutar código extranjero, JSON.parse no puede.
Alexander Mills el
Eso es cierto si y solo si su cadena se inyecta realmente en el tiempo de ejecución JSON de tal manera que omita el analizador. No veo el código aquí haciendo eso de manera confiable. Tírelo de una variable de entorno y páselo, JSON.parse()y sí, es inequívocamente seguro ... pero aquí, el tiempo de ejecución JSON está recibiendo el contenido (no confiable) en banda con el código (confiable).
Charles Duffy el
... de manera similar, si tiene su código, lea el JSON del archivo como una cadena y pase esa cadena a JSON.parse(), también está a salvo, pero eso tampoco está sucediendo aquí.
Charles Duffy el
1
... ahh, diablos, bien podría entrar en el "cómo" de inmediato. El problema es que está sustituyendo la variable de shell, a la que desea pasar JSON.parse(), en el código . Está asumiendo que poner retrocesos literales mantendrá el contenido literal, pero esa es una suposición completamente insegura, porque pueden existir retrocesos literales en el contenido del archivo (y, por lo tanto, la variable), y así pueden terminar la cita y entrar en un contexto sin comillas donde el Los valores se ejecutan como código.
Charles Duffy el
7

Ahora que Powershell es multiplataforma, pensé en abrirme camino, ya que me parece bastante intuitivo y extremadamente simple.

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

ConvertFrom-Json convierte el JSON en un objeto personalizado de Powershell, para que pueda trabajar fácilmente con las propiedades desde ese punto en adelante. Si solo quisiera la propiedad 'id' por ejemplo, simplemente haría esto:

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

Si quisieras invocar todo desde Bash, entonces deberías llamarlo así:

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

Por supuesto, hay una forma pura de Powershell para hacerlo sin rizo, que sería:

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

Finalmente, también está 'ConvertTo-Json' que convierte un objeto personalizado a JSON con la misma facilidad. Aquí hay un ejemplo:

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

Lo que produciría un buen JSON como este:

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

}

Es cierto que usar un shell de Windows en Unix es algo sacrílego, pero Powershell es realmente bueno en algunas cosas, y analizar JSON y XML son algunas de ellas. Esta es la página de GitHub para la versión multiplataforma https://github.com/PowerShell/PowerShell

usuario2233949
fuente
votó porque está promoviendo la nueva estrategia de Microsoft para abrir sus herramientas de código abierto e incorporar herramientas extranjeras de código abierto. Es algo bueno para nuestro mundo.
Alex
No me gustaba PowerShell, pero debo admitir que el manejo de JSON como objetos es bastante agradable.
MartinThé
6

Alguien que también tenga archivos xml, puede que quiera mirar mi Xidel . Es un procesador JSONiq sin dependencia de cli . (es decir, también es compatible con XQuery para el procesamiento xml o json)

El ejemplo en la pregunta sería:

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

O con mi propia sintaxis de extensión no estándar:

 xidel -e 'json("http://twitter.com/users/username.json").name'
BeniBela
fuente
1
O más simple hoy en día: xidel -s https://api.github.com/users/lambda -e 'name'(o -e '$json/name', o -e '($json).name').
Reino
6

No puedo usar ninguna de las respuestas aquí. Sin jq disponible, sin matrices de shell, sin declaración, sin grep -P, sin mirar atrás y sin mirar atrás, sin Python, sin Perl, sin Ruby, no, ni siquiera Bash ... Las respuestas restantes simplemente no funcionan bien. JavaScript sonaba familiar, pero la lata dice Nescaffe, por lo que es un no ir también :) Incluso si estuviera disponible, para mi simple necesidad, serían excesivos y lentos.

Sin embargo, es extremadamente importante para mí obtener muchas variables de la respuesta con formato json de mi módem. ¡Lo estoy haciendo en un sh con BusyBox muy recortado en mis enrutadores! No hay problemas con el uso de awk solo: solo configure delimitadores y lea los datos. ¡Para una sola variable, eso es todo!

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

¿Recuerdas que no tengo matrices? Tuve que asignar dentro de los datos analizados awk a las 11 variables que necesito en un script de shell. Dondequiera que mirara, se decía que era una misión imposible. No hay problema con eso también.

Mi solución es simple. Este código: 1) analizará el archivo .json de la pregunta (en realidad, tomé prestada una muestra de datos de trabajo de la respuesta más votada) y elegirá los datos citados, más 2) creará variables de shell desde el awk asignando un shell con nombre libre nombres de variables

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"

No hay problemas con espacios en blanco. En mi uso, el mismo comando analiza una salida larga de una sola línea. A medida que se utiliza eval, esta solución es adecuada solo para datos confiables. Es sencillo adaptarlo para recoger datos no citados. Para una gran cantidad de variables, la ganancia de velocidad marginal se puede lograr usando else if. La falta de matriz obviamente significa: no hay registros múltiples sin violín adicional. Pero donde hay arreglos disponibles, adaptar esta solución es una tarea simple.

La respuesta de @maikel sed casi funciona (pero no puedo comentarla). Para mis datos bien formateados, funciona. No tanto con el ejemplo utilizado aquí (las comillas faltantes lo descartan). Es complicado y difícil de modificar. Además, no me gusta tener que hacer 11 llamadas para extraer 11 variables. ¿Por qué? Calculé 100 bucles extrayendo 9 variables: ¡la función sed tardó 48,99 segundos y mi solución tardó 0,91 segundos! ¿No es justo? Haciendo una sola extracción de 9 variables: 0.51 vs. 0.02 seg.

Pila
fuente
5

Puedes probar algo como esto:

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

Puedes usar jshon:

curl 'http://twitter.com/users/username.json' | jshon -e text
kev
fuente
El sitio dice: "El doble de rápido, 1/6 de la memoria" ... y luego: "Jshon analiza, lee y crea JSON. Está diseñado para ser lo más utilizable posible dentro del shell y reemplaza a los analizadores frágiles adhoc hechos de grep / sed / awk, así como analizadores de una línea de peso pesado hechos de perl / python. "
Roger
esta figura como la solución recomendada para analizar JSON en Bash
qodeninja
¿Cuál es la forma más fácil de deshacerse de las citas en torno al resultado?
Hombre
4

aquí hay una manera de hacerlo con 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
fuente
4

Para un análisis JSON más complejo, sugiero usar el módulo python jsonpath (por Stefan Goessner):

  1. Instalarlo -

sudo easy_install -U jsonpath

  1. Úselo

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

Analízalo (extrae todos los títulos de libros con precio <10) -

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

Saldrá -

Sayings of the Century
Moby Dick

NOTA: La línea de comando anterior no incluye verificación de errores. Para obtener una solución completa con comprobación de errores, debe crear un pequeño script de Python y ajustar el código con try-except.

shlomosh
fuente
Hermoso idioma. Ni siquiera conozco Python, pero esta parece ser una solución poderosa
Sridhar Sarnobat
Tuve un pequeño problema jsonpathpara instalarlo jsonpath_rw, así que aquí hay algo similar que puedes probar si lo anterior no funciona: 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;"(Utilicé la ruta completa al binario de Python porque estaba teniendo algunos problemas con múltiples pitones instalado).
Sridhar Sarnobat
4

Si tienes php :

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

Por ejemplo:
tenemos un recurso que proporciona a json códigos iso de países: http://country.io/iso3.json y podemos verlo fácilmente en un shell con curl:

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

pero no parece muy conveniente, y no es legible, mejor analice json y vea la estructura legible:

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',
  ...

Si tiene matrices anidadas, esta salida se verá mucho mejor ...

Espero que esto sea útil ...

V. Kovpak
fuente
4

También hay una muy simple pero potente JSON CLI procesamiento herramienta fx - https://github.com/antonmedv/fx

Ejemplo de formato JSON en terminal Bash

Ejemplos

Use la función anónima:

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

Si no pasa la función anónima param => ..., el código se transformará automáticamente en función anónima. Y puede obtener acceso a JSON con esta palabra clave:

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

O simplemente use la sintaxis de puntos también:

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

Puede pasar cualquier cantidad de funciones anónimas para reducir JSON:

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

Puede actualizar JSON existente utilizando el operador de propagación:

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

Simplemente JavaScript . No es necesario aprender una nueva sintaxis.


ACTUALIZACIÓN 2018-11-06

fxahora tiene modo interactivo ( ! )

https://github.com/antonmedv/fx

Anton Medvedev
fuente
77
Si está promocionando su propia creación, debe ser explícito al respecto. Vea Cómo no ser un spammer.
tripleee
4

Esta es otra bashy pythonrespuesta híbrido. Publiqué esta respuesta porque quería procesar resultados JSON más complejos, pero reduciendo la complejidad de mi aplicación bash. Quiero abrir el siguiente objeto JSON de http://www.arcgis.com/sharing/rest/info?f=json en bash:

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

En el siguiente ejemplo, creé mi propia implementación jqy unquoteapalancamiento python. Notarás que una vez que importamos el objeto python de jsonun diccionario python, podemos usar la sintaxis de python para navegar por el diccionario. Para navegar por lo anterior, la sintaxis es:

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

Al usar magic en bash, omitimos datay solo suministramos el texto de python a la derecha de los datos, es decir

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

Tenga en cuenta que, sin parámetros, jqactúa como un prettifier JSON. Con los parámetros podemos usar la sintaxis de Python para extraer todo lo que queramos del diccionario, incluida la navegación de subdiccionarios y elementos de matriz.

Aquí hay un ejemplo de trabajo que demuestra lo anterior:

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
fuente
3

He hecho esto, "analizando" una respuesta json para un valor particular, de la siguiente manera:

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

Claramente, $ url aquí sería la url de twitter, y $ var sería "texto" para obtener la respuesta para esa var.

Realmente, creo que lo único que estoy haciendo que el OP ha dejado fuera es grep para la línea con la variable específica que busca. Awk toma el segundo elemento en la línea y con sed elimino las comillas.

Alguien más inteligente que yo probablemente podría pensar todo con awk o grep.

Ahora, puedes hacerlo todo con solo sed:

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

por lo tanto, no awk, no grep ... No sé por qué no pensé en eso antes. Hmmm ...

tonybaldwin
fuente
En realidad, con sed que puedes hacer
tonybaldwin
1
Las tuberías grep | awk | sedy sed | sed | sedson antipatrones derrochadores. Su último ejemplo puede reescribirse fácilmente, curl "$url" | sed '/text/!d;s/\"text\"://g;s/\"//g;s/\ //g'pero como otros han señalado, este es un enfoque propenso a errores y frágil que no debería recomendarse en primer lugar.
tripleee
Tuve que usar grep -oPz 'name \ ": \". *? \ "' Curloutput | sed 's / name \": / \ n / g'
Ferroao
3

Analizar JSON es doloroso en un script de shell. Con un lenguaje más apropiado, cree una herramienta que extraiga atributos JSON de una manera consistente con las convenciones de scripting de shell. Puede usar su nueva herramienta para resolver el problema inmediato de scripts de shell y luego agregarlo a su kit para futuras situaciones.

Por ejemplo, considere una herramienta jsonlookup de modo que si digo jsonlookup access token idque devolverá el ID de atributo definido dentro del token de atributo definido dentro del acceso de atributo desde stdin, que presumiblemente son datos JSON. Si el atributo no existe, la herramienta no devuelve nada (estado de salida 1). Si falla el análisis, salga del estado 2 y envíe un mensaje a stderr. Si la búsqueda tiene éxito, la herramienta imprime el valor del atributo.

Después de haber creado una herramienta Unix con el propósito preciso de extraer valores JSON, puede usarla fácilmente en scripts de shell:

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

Cualquier lenguaje servirá para la implementación de jsonlookup . Aquí hay una versión de 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
fuente
3

Un trazador de líneas que usa python. Funciona particularmente bien si está escribiendo un solo archivo .sh y no desea depender de otro archivo .py. También aprovecha el uso de tubería |. echo "{\"field\": \"value\"}"se puede reemplazar por cualquier cosa que imprima un json en la salida estándar.

echo "{\"field\": \"value\"}" | python -c 'import sys, json
print(json.load(sys.stdin)["field"])'
Adam Kurkiewicz
fuente
La pregunta no era buscar una solución Python. Vea los comentarios también.
Andrew Barber
3

Este es un buen caso de uso para pythonpy :

curl 'http://twitter.com/users/username.json' | py 'json.load(sys.stdin)["name"]'
RussellStewart
fuente
Aún más corto, módulo python -c aquí :) agradable.
m3nda