Analyser JSON avec les outils Unix


880

J'essaie d'analyser le JSON renvoyé d'une demande de boucle, comme ceci:

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

Ce qui précède divise le JSON en champs, par exemple:

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

Comment imprimer un champ spécifique (indiqué par le -v k=text)?


5
Euh ce n'est pas bon json en analysant btw ... qu'en est-il des caractères d'échappement dans les chaînes ... etc Y a-t-il une réponse python à cela sur SO (une réponse perl même ...)?
martinr

51
Chaque fois que quelqu'un dit "le problème X peut facilement être résolu avec une autre langue Y", c'est le code pour "ma boîte à outils n'a qu'une pierre pour enfoncer les clous ... pourquoi s'embêter avec autre chose?"
BryanH

22
@BryanH: sauf que parfois la langue Y peut être plus équipée pour résoudre un problème particulier X quel que soit le nombre de langues que la personne qui a suggéré Y connaît.
jfs

15
Un peu tard, mais c'est parti. grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json. Cela résout la tâche facilement et uniquement avec grep et fonctionne parfaitement pour les JSON simples. Pour les JSON complexes, vous devez utiliser un analyseur approprié.
diosney

2
@auser, seriez-vous d'accord avec une modification changeant "avec sed et awk" en "avec les outils UNIX" dans le titre?
Charles Duffy

Réponses:


1128

Il existe un certain nombre d'outils spécialement conçus pour manipuler JSON à partir de la ligne de commande, et seront beaucoup plus faciles et plus fiables que de le faire avec Awk, tels que jq:

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

Vous pouvez également le faire avec des outils qui sont probablement déjà installés sur votre système, comme Python en utilisant le jsonmodule , et ainsi éviter toute dépendance supplémentaire, tout en bénéficiant d'un analyseur JSON approprié. Ce qui suit suppose que vous souhaitez utiliser UTF-8, dans lequel le JSON d'origine doit être codé et c'est ce que la plupart des terminaux modernes utilisent également:

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

Notes historiques

Cette réponse recommandait à l'origine jsawk , qui devrait toujours fonctionner, mais est un peu plus lourd à utiliser que jq, et dépend d'un interpréteur JavaScript autonome en cours d'installation qui est moins courant qu'un interprète Python, donc les réponses ci-dessus sont probablement préférables:

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

Cette réponse a également utilisé à l'origine l'API Twitter de la question, mais cette API ne fonctionne plus, ce qui rend difficile la copie des exemples à tester, et la nouvelle API Twitter nécessite des clés d'API, j'ai donc basculé vers l'utilisation de l'API GitHub qui peut être utilisé facilement sans clés API. La première réponse à la question d'origine serait:

curl 'http://twitter.com/users/username.json' | jq -r '.text'

7
@thrau +1. jq il est disponible dans le référentiel et super facile à utiliser donc c'est bien mieux que jsawk. J'ai testé les deux pendant quelques minutes, jq a gagné cette bataille
Szymon Sadło

1
Notez que dans Python 2, si vous redirigez la sortie vers une autre commande, l' printinstruction sera toujours codée en ASCII car vous utilisez Python dans un canal. Insérez PYTHONIOENCODING=<desired codec>dans la commande pour définir un codage de sortie différent, adapté à votre terminal. En Python 3, la valeur par défaut est UTF-8 dans ce cas (en utilisant la print() fonction ).
Martijn Pieters

1
Installez jq sur OSX avec brew install jq
Andy Fraley

1
curl -sest équivalent à curl --silent, tandis que jq -rsignifie jq --raw-outputie sans guillemets.
Serge Stroobandt

python -c "importe les demandes; r = requests.get (' api.github.com/users/lambda');print r.json () [' name '];" . Le plus simple!
NotTooTechy

277

Pour extraire rapidement les valeurs d'une clé particulière, j'aime personnellement utiliser "grep -o", qui ne renvoie que la correspondance de l'expression régulière. Par exemple, pour obtenir le champ "texte" des tweets, quelque chose comme:

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

Cette expression régulière est plus robuste que vous ne le pensez; par exemple, il traite très bien les chaînes ayant des virgules intégrées et des guillemets échappés à l'intérieur. Je pense qu'avec un peu plus de travail, vous pourriez en faire un qui soit garanti d'extraire la valeur, s'il est atomique. (S'il a une imbrication, un regex ne peut pas le faire bien sûr.)

Et pour nettoyer plus (mais en gardant Escaping originale de la chaîne) , vous pouvez utiliser quelque chose comme: | perl -pe 's/"text"://; s/^"//; s/",$//'. (Je l'ai fait pour cette analyse .)

Pour tous les ennemis qui insistent sur le fait que vous devriez utiliser un véritable analyseur JSON - oui, c'est essentiel pour l'exactitude, mais

  1. Pour effectuer une analyse très rapide, comme compter les valeurs pour vérifier les bogues de nettoyage des données ou avoir une idée générale des données, il est plus rapide de frapper quelque chose sur la ligne de commande. Ouvrir un éditeur pour écrire un script est distrayant.
  2. grep -oest un ordre de grandeur plus rapide que la jsonbibliothèque standard Python , du moins lors de cette opération pour les tweets (qui font environ 2 Ko chacun). Je ne sais pas si c'est juste parce que jsonc'est lent (je devrais comparer avec yajl un jour); mais en principe, une expression régulière devrait être plus rapide car elle est à l'état fini et beaucoup plus optimisable, au lieu d'un analyseur qui doit prendre en charge la récursivité, et dans ce cas, dépense beaucoup d'arbres de construction CPU pour les structures qui ne vous intéressent pas. (Si quelqu'un écrivait un transducteur à états finis qui effectuait une analyse JSON appropriée (limitée en profondeur), ce serait fantastique! En attendant, nous avons "grep -o".)

Pour écrire du code maintenable, j'utilise toujours une vraie bibliothèque d'analyse. Je n'ai pas essayé jsawk , mais si cela fonctionne bien, cela répondrait au point # 1.

Une dernière solution, plus farfelue: j'ai écrit un script qui utilise Python jsonet extrait les clés que vous voulez, dans des colonnes séparées par des tabulations; puis je passe à travers un wrapper awkqui permet un accès nommé aux colonnes. Ici: les scripts json2tsv et tsvawk . Donc pour cet exemple ce serait:

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

Cette approche ne prend pas en compte le # 2, est plus inefficace qu'un seul script Python, et elle est un peu fragile: elle force la normalisation des sauts de ligne et des tabulations dans les valeurs de chaîne, pour bien jouer avec la vue du monde délimitée par les enregistrements / champs d'AWK. Mais cela vous permet de rester sur la ligne de commande, avec plus d'exactitude que grep -o.


11
Vous avez oublié les valeurs entières. grep -Po '"text":(\d*?,|.*?[^\\]",)'
Robert

3
Robert: Oui, mon expression rationnelle a été écrite uniquement pour les valeurs de chaîne de ce champ. Des entiers pourraient être ajoutés comme vous le dites. Si vous voulez tous les types, vous devez en faire de plus en plus: booléens, null. Et les tableaux et les objets nécessitent plus de travail; seule une profondeur limitée est possible, sous des expressions régulières.
Brendan OConnor

9
1. jq .namefonctionne sur la ligne de commande et ne nécessite pas "d'ouvrir un éditeur pour écrire un script". 2. Peu importe la vitesse à laquelle votre expression
régulière

6
et si vous ne voulez que les valeurs, vous pouvez simplement y jeter un œil. | grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
JeffCharter

34
Il semble que sous OSX, l' -Poption soit manquante. Je l' ai testé sur OSX 10.11.5 et grep --versionétait grep (BSD grep) 2.5.1-FreeBSD. Je l'ai fait fonctionner avec l'option "regex étendu" sur OSX. La commande d'en haut serait grep -Eo '"text":.*?[^\\]",' tweets.json.
Jens

174

Sur la base que certaines des recommandations ici (en particulier dans les commentaires) suggéraient l'utilisation de Python, j'ai été déçu de ne pas trouver d'exemple.

Donc, voici une ligne pour obtenir une valeur unique à partir de certaines données JSON. Il suppose que vous canalisez les données (de quelque part) et devrait donc être utile dans un contexte de script.

echo '{"hostname":"test","domainname":"example.com"}' | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["hostname"]'

J'ai amélioré cette réponse ci-dessous pour utiliser une fonction bash: curl 'some_api' | getJsonVal 'key'
Joe Heyming

pythonpy( github.com/russell91/pythonpy est presque toujours une meilleure alternative à python -c, bien qu'il doive être installé avec pip. redirigez simplement le json vers py --ji -x 'x[0]["hostname"]'. Si vous ne vouliez pas utiliser le support json_input intégré, vous pourriez toujours obtenir ceux-ci importent automatiquement en tant quepy 'json.loads(sys.stdin)[0]["hostname"]'
RussellStewart

2
Merci! Pour une analyse JSON plus rapide et plus sale, je l'ai enveloppé dans une fonction bash: jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); print($1)"; }pour que je puisse écrire: curl ...... | jsonq 'json.dumps([key["token"] for key in obj], indent=2)'& plus de choses effrayantes similaires ... Btw, obj[0]semble inutile, il semble que ça objfonctionne bien dans les cas par défaut (?).
akavel

Merci. J'ai rendu ce respect JSON un peu meilleur que l'impression:jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); sys.stdout.write(json.dumps($1))"; }
Adam K Dean

4
obj[0]provoque une erreur lors de l'analyse { "port":5555 }. Fonctionne bien après le retrait [0].
CyberEd

134

Suivant l'exemple de MartinR et Boecko:

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

Cela vous donnera une sortie extrêmement conviviale pour grep. Très pratique:

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

37
Comment extrairez-vous une clé spécifique, comme le demande OP?
juan

2
Meilleure réponse jusqu'à présent à mon humble avis, pas besoin d'installer quoi que ce soit d'autre sur la plupart des distributions et vous pouvez | grep field. Merci!
Andrea Richiardi

7
Tout cela ne fait que formater le JSON, si je ne me trompe pas. Il ne permet pas à l'appelant de sélectionner un champ particulier dans la sortie, comme le ferait une solution xpath ou quelque chose basé sur "JSON Pointer".
Cheeso

4
Je me retrouve juste avec une paire de valeurs clés, mais pas la valeur en soi.
christopher

1
jqn'est généralement pas installé alors que python l'est. De plus, une fois que vous êtes en Python, vous pourriez aussi bien faire tout le chemin et l'analyser avecimport json...
CpILL

125

Vous pouvez simplement télécharger le jqbinaire pour votre plate - forme et exécuter ( chmod +x jq):

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

Il extrait l' "name"attribut de l'objet json.

jqla page d'accueil dit que c'est comme sedpour les données JSON.


27
Juste pour mémoire, jqest un outil incroyable.
hoss

2
D'accord. Je ne peux pas comparer avec jsawk de la réponse acceptée, car je ne l'ai pas utilisé, mais pour l'expérimentation locale (où l'installation d'un outil est acceptable), je recommande fortement jq. Voici un exemple un peu plus étendu, qui prend chaque élément d'un tableau et synthétise un nouvel objet JSON avec des données sélectionnées: curl -s https://api.example.com/jobs | jq '.jobs[] | {id, o: .owner.username, dateCreated, s: .status.state}'
jbyler

2
Aime ça. Très léger, et comme il est en vieux C simple, il peut être compilé à peu près n'importe où.
Benmj

1
Le plus pratique: il n'a pas besoin de bibliothèques tierces (contrairement à jsawk) et est facile à installer (OSX: brew install jq)
lauhub

1
C'est la réponse la plus pratique et la plus facile à mettre en œuvre pour mon cas d'utilisation. Pour le système Ubuntu (14.04), un simple jq d'installation apt-get a ajouté l'outil à mon système. Je transfère la sortie JSON des réponses de l'AWS CLI dans jq et cela fonctionne très bien pour extraire des valeurs de certaines clés imbriquées dans la réponse.
Brandon K

105

Utilisation de Node.js

Si le système a installé, il est possible d'utiliser les indicateurs de script -pprint et -eevaulate avec JSON.parsepour extraire toute valeur nécessaire.

Un exemple simple utilisant la chaîne JSON { "foo": "bar" }et extrayant la valeur de "foo":

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

Étant donné que nous avons accès à d' catautres utilitaires, nous pouvons les utiliser pour les fichiers:

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

Ou tout autre format tel qu'une URL contenant du JSON:

$ node -pe 'JSON.parse(process.argv[1]).name' "$(curl -s https://api.github.com/users/trevorsenior)"
Trevor Senior

1
Merci! mais dans mon cas, cela ne fonctionne qu'avec le drapeau -enode -p -e 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
Rnd_d

33
Pipes! curl -s https://api.github.com/users/trevorsenior | node -pe "JSON.parse(require('fs').readFileSync('/dev/stdin').toString()).name"
nicerobot

4
c'est ma solution préférée; utiliser un langage (javascript) pour analyser une structure de données qui lui est naturelle (JSON). semble le plus correct . aussi - le noeud est probablement déjà disponible sur le système, et vous n'aurez pas à manipuler avec les binaires de jq (qui ressemble à un autre bon choix).
Eliran Malka

Voici la fonction de script bash: # jsonv obtient la valeur d'objet json pour un attribut spécifique # premier paramètre est le document json # deuxième paramètre est l'attribut dont la valeur doit être retournée get_json_attribute_value () {node -pe 'JSON.parse (process. argv [1]) [process.argv [2]] '"$ 1" "$ 2"}
Youness

6
Ce qui suit fonctionne avec Node.js 10:cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
Ilya Boyandin

100

Utilisez le support JSON de Python au lieu d'utiliser awk!

Quelque chose comme ça:

curl -s http://twitter.com/users/username.json | \
    python -c "import json,sys;obj=json.load(sys.stdin);print obj['name'];"

6
Pardonnez-moi d'avoir essayé de trouver une bonne réponse ...: Je vais essayer plus fort. La partisanerie exige plus que l'écriture d'un script awk pour le secouer!
martinr

9
Pourquoi utilisez-vous la variable obj dans cette solution oneliner?. C'est inutile et n'est pas stocké de toute façon? Vous écrivez moins en utilisant json.load(sys.stdin)['"key']"comme exemple comme: curl -sL httpbin.org/ip | python -c "import json,sys; print json.load(sys.stdin)['origin']".
m3nda

65

Vous avez demandé comment vous tirer une balle dans le pied et je suis ici pour fournir les munitions:

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

Vous pouvez utiliser à la tr -d '{}'place de sed. Mais les laisser complètement de côté semble également avoir l'effet souhaité.

Si vous souhaitez supprimer les guillemets extérieurs, canalisez le résultat de ce qui précède à travers sed 's/\(^"\|"$\)//g'

Je pense que d'autres ont suffisamment sonné l'alarme. Je serai avec un téléphone portable pour appeler une ambulance. Tirez quand vous êtes prêt.


10
De cette façon, la folie se trouve, lisez ceci: stackoverflow.com/questions/1732348/…
pause jusqu'à nouvel ordre.

3
J'ai lu toutes les réponses et celle-ci fonctionne parfaitement pour moi sans aucune dépendance supplémentaire. +1
eth0

Voilà ce que je cherchais. La seule correction - la commande sed fournie pour supprimer les guillemets n'a pas fonctionné pour moi, j'ai plutôt utilisé sed 's / "// g'
AlexG

44

Utiliser Bash avec Python

Créez une fonction bash dans votre fichier .bash_rc

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

alors

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

Voici la même fonction, mais avec vérification des erreurs.

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

Où $ # -ne 1 garantit au moins 1 entrée et -t 0 vous assure que vous redirigez depuis un tube.

La bonne chose à propos de cette implémentation est que vous pouvez accéder aux valeurs json imbriquées et obtenir json en retour! =)

Exemple:

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

Si vous voulez être vraiment fantaisiste, vous pouvez imprimer les données:

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

One-liner sans la fonction 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 vous voulez qu'il fonctionne avec les deux python 2 et 3.
Par Johansson

Je pense que cela devrait changer en system.stdout.write (obj $ 1). De cette façon, vous pouvez dire: getJsonVal "['environnement'] ['nom']", comme l'exemple de
@Cheeso

1
@Narek Dans ce cas, cela ressemblerait à ceci: fonctiongetJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
Joe Heyming

30

TickTick est un analyseur JSON écrit en bash (<250 lignes de code)

Voici l'extrait de l'auteur de son article, Imaginez un monde où Bash prend en charge 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

2
Comme la seule réponse pure-bash robuste ici, cela mérite plus de votes positifs.
Ed Randall

Existe-t-il un moyen d'imprimer à nouveau cette variable de personnes dans une chaîne json? Ce serait extrêmement utile
Thomas Fournet

1
Enfin une réponse ne recommandant pas Python ou d'autres méthodes atroces ... Merci!
Akito

21

Analyser JSON avec PHP CLI

Sans doute hors sujet, mais puisque la priorité règne, cette question reste incomplète sans mentionner notre PHP fidèle et fidèle, ai-je raison?

En utilisant le même exemple JSON mais permet de l'affecter à une variable pour réduire l'obscurité.

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

Maintenant pour la qualité PHP, en utilisant file_get_contents et le wrapper de flux php: // stdin .

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

ou comme indiqué en utilisant fgets et le flux déjà ouvert à la constante CLI STDIN .

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

nJoy!


Vous pouvez même utiliser $argnau lieu defgets(STDIN)
IcanDivideBy0

Oups, $argnfonctionne avec l'indicateur -E ou -R et seulement si le contenu JSON est sur une seule ligne ...
IcanDivideBy0

21

Version native de Bash: fonctionne également bien avec les barres obliques inverses (\) et les guillemets (")

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":"john@doe.com"}' username
parse_json '{"username":"john doe","email":"john@doe.com"}' email

--- outputs ---

john, doe
johh@doe.com

C'est génial. Mais si la chaîne JSON contient plusieurs clés de messagerie, l'analyseur affichera john@doe.com "" john@doe.com
rtc11

Ne fonctionne pas s'il y a un tiret dans l'e-mail comme jean-pierre@email.com
alexmngn

13

Version qui utilise Ruby et http://flori.github.com/json/

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

ou plus concis:

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

3
c'est mon préféré;) BTW vous pouvez le raccourcir avec ruby ​​-rjson pour exiger la bibliothèque
lucapette

Notez que la finale ;n'est pas requise dans Ruby (elle n'est utilisée que pour concaténer des instructions qui se trouveraient normalement sur des lignes distinctes en une seule ligne).
Zack Morris

11

Malheureusement , la réponse qui a voté top utilisations greprenvoie le plein match qui n'a pas fonctionné dans mon scénario, mais si vous connaissez le format JSON reste constante , vous pouvez utiliser lookbehind et préanalyse pour extraire uniquement les valeurs souhaitées.

# 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

Vous ne connaissez jamais réellement l'ordre des éléments dans un dictionnaire JSON. Ils sont, par définition, non ordonnés. C'est précisément l'une des raisons fondamentales pour lesquelles rouler votre propre analyseur JSON est une approche condamnée.
tripleee

10

Si quelqu'un veut simplement extraire des valeurs à partir d'objets JSON simples sans avoir besoin de structures imbriquées, il est possible d'utiliser des expressions régulières sans même quitter le bash.

Voici une fonction que j'ai définie en utilisant des expressions régulières bash basées sur la norme 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
}

Avertissements: les objets et les tableaux ne sont pas pris en charge en tant que valeur, mais tous les autres types de valeur définis dans la norme sont pris en charge. En outre, une paire sera mise en correspondance, quelle que soit la profondeur dans le document JSON, tant qu'elle a exactement le même nom de clé.

En utilisant l'exemple d'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 peut-on extraire des valeurs de propriétés imbriquées avec cette fonction?
vsbehere

8

Il existe un moyen plus simple d'obtenir une propriété à partir d'une chaîne json. En utilisant un package.jsonfichier comme exemple, essayez ceci:

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

Nous utilisons process.envcar cela obtient le contenu du fichier dans node.js sous forme de chaîne sans aucun risque de contenu malveillant échappant à leur citation et analysé en tant que code.


L'utilisation de la concaténation de chaînes pour substituer des valeurs dans une chaîne analysée en tant que code permet d'exécuter du code node.js arbitraire, ce qui signifie qu'il est extrêmement dangereux de l'utiliser avec du contenu aléatoire que vous avez obtenu sur Internet. Il y a une raison pour que les méthodes sûres / meilleures pratiques pour analyser le JSON en JavaScript ne se contentent pas de l'évaluer.
Charles Duffy

@CharlesDuffy n'est pas sûr de suivre, mais l'appel JSON.parse devrait être plus sûr, car il require()peut réellement exécuter du code étranger, JSON.parse ne peut pas.
Alexander Mills

C'est vrai si et seulement si votre chaîne est réellement injectée dans le runtime JSON de manière à contourner l'analyseur. Je ne vois pas le code ici faire cela de manière fiable. Tirez-le d'une variable d'environnement et passez-le à JSON.parse()et oui, vous êtes sûr sans ambiguïté ... mais ici, le runtime JSON reçoit le contenu (non approuvé) en bande avec le code (de confiance).
Charles Duffy

... de même, si votre code lit le JSON du fichier sous forme de chaîne et transmet cette chaîne à JSON.parse(), vous êtes également en sécurité, mais cela ne se produit pas non plus ici.
Charles Duffy

1
... ahh, diable, pourrait tout aussi bien entrer dans le "comment" immédiatement. Le problème est que vous remplacez la variable shell, à laquelle vous avez l'intention de passer JSON.parse(), dans le code . Vous en supposant que la mise des accents graves littérales gardera le contenu littéral, mais c'est une hypothèse tout à fait dangereuse, parce que les apostrophes inverses littérales peuvent exister dans le contenu du fichier (et donc la variable), et peut ainsi mettre fin à la citation et entrer dans un contexte non coté où le les valeurs sont exécutées sous forme de code.
Charles Duffy

7

Maintenant que Powershell est multiplateforme, je pensais que j'allais me lancer là-bas, car je le trouve assez intuitif et extrêmement simple.

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

ConvertFrom-Json convertit le JSON en un objet personnalisé Powershell, de sorte que vous pouvez facilement travailler avec les propriétés à partir de ce point. Si vous vouliez seulement la propriété 'id' par exemple, vous feriez juste ceci:

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

Si vous vouliez invoquer le tout depuis Bash, alors vous devriez l'appeler comme ceci:

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

Bien sûr, il existe une manière Powershell pure de le faire sans boucle, qui serait:

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

Enfin, il existe également 'ConvertTo-Json' qui convertit tout aussi facilement un objet personnalisé en JSON. Voici un exemple:

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

Ce qui produirait un joli JSON comme celui-ci:

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

}

Certes, l'utilisation d'un shell Windows sur Unix est quelque peu sacrilège, mais Powershell est vraiment bon dans certaines choses, et l'analyse JSON et XML en sont quelques-unes. Ceci est la page GitHub pour la version multiplateforme https://github.com/PowerShell/PowerShell


voté parce que vous faites la promotion de la nouvelle stratégie Microsoft pour open-source leurs outils et incorporer des outils étrangers open-source. C'est une bonne chose pour notre monde.
Alex

Auparavant, je n'aimais pas PowerShell, mais je dois admettre que la gestion de JSON en tant qu'objets est plutôt agréable.
MartinThé

6

Quelqu'un qui a également des fichiers xml, voudra peut-être regarder mon Xidel . Il s'agit d'un processeur JSONiq sans cli et sans dépendance . (c'est-à-dire qu'il prend également en charge XQuery pour le traitement xml ou json)

L'exemple dans la question serait:

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

Ou avec ma propre syntaxe d'extension non standard:

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

1
Ou plus simple de nos jours: xidel -s https://api.github.com/users/lambda -e 'name'(ou -e '$json/name', ou -e '($json).name').
Reino

6

Je ne peux utiliser aucune des réponses ici. Pas de jq disponible, pas de tableaux de shell, pas de déclaration, pas de grep -P, pas de lookbehind et lookahead, pas de Python, pas de Perl, pas de Ruby, non - pas même Bash ... Les réponses restantes ne fonctionnent tout simplement pas bien. JavaScript semblait familier, mais l'étain dit Nescaffe - donc c'est non plus :) Même si disponible, pour mon simple besoin - ils seraient exagérés et lents.

Pourtant, il est extrêmement important pour moi d'obtenir de nombreuses variables de la réponse au format json de mon modem. Je le fais dans un sh avec BusyBox très coupé vers le bas à mes routeurs! Aucun problème en utilisant awk seul: il suffit de définir des délimiteurs et de lire les données. Pour une seule variable, c'est tout!

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

Rappelez-vous que je n'ai pas de tableaux? J'ai dû affecter dans les données analysées awk aux 11 variables dont j'ai besoin dans un script shell. Partout où je regardais, c'était une mission impossible. Pas de problème avec ça aussi.

Ma solution est simple. Ce code va: 1) analyser le fichier .json de la question (en fait, j'ai emprunté un échantillon de données de travail à la réponse la plus votée) et sélectionner les données citées, plus 2) créer des variables de shell à l'intérieur de l'awk en attribuant un shell nommé gratuit noms 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"

Aucun problème avec les blancs à l'intérieur. Dans mon utilisation, la même commande analyse une sortie longue ligne unique. Comme eval est utilisé, cette solution convient uniquement aux données fiables. Il est simple de l'adapter à la collecte de données non cotées. Pour un grand nombre de variables, un gain de vitesse marginal peut être obtenu en utilisant else if. Manque de tableau signifie évidemment: pas d'enregistrements multiples sans bidouilles supplémentaires. Mais là où des baies sont disponibles, l'adaptation de cette solution est une tâche simple.

@maikel sed répond presque fonctionne (mais je ne peux pas en parler). Pour mes données bien formatées - cela fonctionne. Pas tellement avec l'exemple utilisé ici (les guillemets manquants le jettent). C'est compliqué et difficile à modifier. De plus, je n'aime pas avoir à faire 11 appels pour extraire 11 variables. Pourquoi? J'ai chronométré 100 boucles en extrayant 9 variables: la fonction sed a pris 48,99 secondes et ma solution a pris 0,91 secondes! Pas juste? Faire une seule extraction de 9 variables: 0,51 contre 0,02 s.


5

Vous pouvez essayer quelque chose comme ça -

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

5

Vous pouvez utiliser jshon:

curl 'http://twitter.com/users/username.json' | jshon -e text

Le site dit: "Deux fois plus vite, 1/6 de la mémoire" ... et ensuite: "Jshon analyse, lit et crée JSON. Il est conçu pour être aussi utilisable que possible depuis l'intérieur de la coque et remplace les fragiles analyseurs adhoc fabriqués à partir de grep / sed / awk ainsi que des analyseurs lourds d'une ligne en perl / python. "
Roger

cela est répertorié comme la solution recommandée pour l'analyse JSON dans Bash
qodeninja

quelle est la façon la plus simple de se débarrasser des guillemets autour du résultat?
gMale

4

voici une façon de le faire avec 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
        }
    }
}'

4

Pour une analyse JSON plus complexe, je suggère d'utiliser le module python jsonpath (par Stefan Goessner) -

  1. Installez-le -

sudo easy_install -U jsonpath

  1. Utilise le -

Exemple de fichier.json (depuis 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
    }
  }
}

Analyser (extraire tous les titres de livres avec un prix <10) -

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

Sortira -

Sayings of the Century
Moby Dick

REMARQUE: la ligne de commande ci-dessus n'inclut pas la vérification des erreurs. pour une solution complète avec vérification des erreurs, vous devez créer un petit script python et encapsuler le code avec try-except.


bel idiome. Je ne connais même pas Python, mais cela semble être une solution puissante
Sridhar Sarnobat

J'avais un peu de mal à installer jsonpathdonc installé à la jsonpath_rwplace, alors voici quelque chose de similaire que vous pouvez essayer si ce qui précède ne fonctionne pas: 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;"(J'ai utilisé le chemin complet vers le binaire python parce que j'avais des problèmes avec plusieurs pythons installée).
Sridhar Sarnobat

4

Si vous avez php :

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

Par exemple:
nous avons une ressource qui fournit à json les codes iso des pays: http://country.io/iso3.json et nous pouvons facilement le voir dans un shell avec curl:

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

mais cela ne semble pas très pratique et pas lisible, mieux analyser json et voir la structure lisible:

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

Ce code imprimera quelque chose comme:

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

si vous avez des tableaux imbriqués, cette sortie sera bien meilleure ...

J'espère que cela vous sera utile ...


4

Il existe également un outil de traitement JSON CLI très simple mais puissant fx - https://github.com/antonmedv/fx

Exemple de formatage JSON dans le terminal Bash

Exemples

Utilisez une fonction anonyme:

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

Si vous ne transmettez pas la fonction anonyme param => ..., le code sera automatiquement transformé en fonction anonyme. Et vous pouvez accéder à JSON par ce mot-clé:

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

Ou utilisez également la syntaxe par points:

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

Vous pouvez transmettre un nombre illimité de fonctions anonymes pour réduire JSON:

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

Vous pouvez mettre à jour le JSON existant à l'aide de l'opérateur d'étalement:

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

Tout simplement JavaScript . Pas besoin d'apprendre une nouvelle syntaxe.


MISE À JOUR 2018-11-06

fxa maintenant le mode interactif ( ! )

https://github.com/antonmedv/fx


7
Si vous faites la promotion de votre propre création, vous devez être explicite à ce sujet. Voir Comment ne pas être un spammeur.
tripleee

4

Ceci est encore une autre bashet pythonréponse hybride. J'ai posté cette réponse parce que je voulais traiter une sortie JSON plus complexe, mais en réduisant la complexité de mon application bash. Je souhaite ouvrir l'objet JSON suivant à partir de http://www.arcgis.com/sharing/rest/info?f=json dans bash:

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

Dans l'exemple suivant, j'ai créé ma propre implémentation jqet unquoteoptimisation python. Vous remarquerez qu'une fois que nous importons l'objet python depuis jsondans un dictionnaire python, nous pouvons utiliser la syntaxe python pour naviguer dans le dictionnaire. Pour naviguer dans ce qui précède, la syntaxe est la suivante:

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

En utilisant la magie dans bash, nous omettons dataet ne fournissons que le texte python à droite des données, c'est-à-dire

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

Notez, sans paramètres, jqagit comme un prettifier JSON. Avec les paramètres, nous pouvons utiliser la syntaxe python pour extraire tout ce que nous voulons du dictionnaire, y compris la navigation dans les sous-répertoires et les éléments de tableau.

Voici un exemple de travail qui illustre ce qui précède:

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

3

J'ai fait cela, "analysant" une réponse json pour une valeur particulière, comme suit:

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

Clairement, $ url ici serait l'url de twitter, et $ var serait "text" pour obtenir la réponse pour cette var.

Vraiment, je pense que la seule chose que je fais l'OP a laissé de côté est grep pour la ligne avec la variable spécifique qu'il cherche. Awk saisit le deuxième élément de la ligne et avec sed, je supprime les guillemets.

Quelqu'un plus intelligent que moi pourrait probablement penser le tout avec awk ou grep.

Maintenant, vous pouvez tout faire avec simplement sed:

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

donc pas d'awk, pas de grep ... Je ne sais pas pourquoi je n'y avais pas pensé avant. Hmmm ...


En fait, avec sed, vous pouvez le faire
tonybaldwin

1
Les pipelines grep | awk | sedet sed | sed | sedsont des antipatterns inutiles. Votre dernier exemple peut facilement être réécrit, curl "$url" | sed '/text/!d;s/\"text\"://g;s/\"//g;s/\ //g'mais comme d'autres l'ont souligné, c'est une approche sujette aux erreurs et fragile qui ne devrait pas être recommandée en premier lieu.
tripleee

J'ai dû utiliser grep -oPz 'name \ ": \". *? \ "' Curloutput | sed 's / name \": / \ n / g'
Ferroao

3

L'analyse JSON est pénible dans un script shell. Avec un langage plus approprié, créez un outil qui extrait les attributs JSON d'une manière cohérente avec les conventions de script shell. Vous pouvez utiliser votre nouvel outil pour résoudre le problème immédiat de script shell et l'ajouter ensuite à votre kit pour des situations futures.

Par exemple, considérons un outil jsonlookup tel que si je dis jsonlookup access token idqu'il renverra l' ID d' attribut défini dans le jeton d' attribut défini dans l' accès d' attribut de stdin, qui est vraisemblablement des données JSON. Si l'attribut n'existe pas, l'outil ne renvoie rien (quitter le statut 1). Si l'analyse échoue, quittez l'état 2 et un message à stderr. Si la recherche réussit, l'outil imprime la valeur de l'attribut.

Après avoir créé un outil Unix dans le but précis d'extraire des valeurs JSON, vous pouvez facilement l'utiliser dans des scripts shell:

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

N'importe quel langage fera l'affaire pour l'implémentation de jsonlookup . Voici une version python assez concise:

#!/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

3

Un deux lignes qui utilise du python. Cela fonctionne particulièrement bien si vous écrivez un seul fichier .sh et que vous ne voulez pas dépendre d'un autre fichier .py. Il tire également parti de l'utilisation des tuyaux |. echo "{\"field\": \"value\"}"peut être remplacé par tout ce qui imprime un json sur la sortie standard.

echo "{\"field\": \"value\"}" | python -c 'import sys, json
print(json.load(sys.stdin)["field"])'

La question ne cherchait pas une solution Python. Voir aussi les commentaires.
Andrew Barber

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.