Analisi di JSON con strumenti Unix


879

Sto cercando di analizzare JSON restituito da una richiesta di arricciatura, in questo modo:

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

Quanto sopra divide il JSON in campi, ad esempio:

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

Come si stampa un campo specifico (indicato da -v k=text)?


5
Ehm, non è un buon json che analizza tra ... e i caratteri di escape nelle stringhe ... ecc. C'è una risposta in pitone a SO (una risposta perl anche ...)?
martinr,

51
Ogni volta che qualcuno dice "il problema X può essere facilmente risolto con un'altra lingua Y", questo è il codice per "la mia cassetta degli attrezzi ha solo una roccia per guidare le unghie ... perché preoccuparsi di qualcos'altro?"
BryanH

22
@BryanH: tranne che a volte la lingua Y può essere più attrezzata per risolvere un particolare problema X indipendentemente da quante lingue la persona che ha suggerito Y conosce.
jfs,

15
Un po 'tardi, ma qui va. grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json. Questo risolve il compito facilmente e solo con grep e funziona perfettamente con semplici JSON. Per JSON complessi è necessario utilizzare un parser appropriato.
Diosney,

2
@utente, staresti bene con una modifica che cambia "con sed e awk" in "con strumenti UNIX" nel titolo?
Charles Duffy,

Risposte:


1127

Esistono numerosi strumenti appositamente progettati allo scopo di manipolare JSON dalla riga di comando e saranno molto più semplici e affidabili rispetto a Awk, come ad esempio jq:

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

Puoi anche farlo con strumenti che probabilmente sono già installati sul tuo sistema, come Python usando il jsonmodulo , e quindi evitare eventuali dipendenze extra, pur avendo il vantaggio di un parser JSON adeguato. Quanto segue presuppone che tu voglia usare UTF-8, in cui dovrebbe essere codificato il JSON originale ed è quello che usano anche i terminali più moderni:

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

Note storiche

Questa risposta originariamente raccomandava jsawk , che dovrebbe ancora funzionare, ma è un po 'più complicato da usare rispetto a jq, e dipende dall'installazione di un interprete JavaScript autonomo che è meno comune di un interprete Python, quindi le risposte sopra sono probabilmente preferibili:

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

Questa risposta originariamente utilizzava anche l'API di Twitter dalla domanda, ma quell'API non funziona più, rendendo difficile la copia degli esempi da testare e la nuova API di Twitter richiede chiavi API, quindi sono passato all'utilizzo dell'API GitHub che può essere utilizzato facilmente senza chiavi API. La prima risposta alla domanda originale sarebbe:

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

7
@thrau +1. jq è disponibile nel repository ed è super facile da usare, quindi è molto meglio di jsawk. Ho provato entrambi per alcuni minuti, jq ha vinto questa battaglia
Szymon Sadło il

1
Si noti che in Python 2, se si esegue il piping dell'output a un altro comando, l' printistruzione verrà sempre codificata in ASCII perché si utilizza Python in una pipe. Inserisci PYTHONIOENCODING=<desired codec>nel comando per impostare una codifica di output diversa, adatta al tuo terminale. In Python 3, il valore predefinito è UTF-8 in questo caso (usando la print() funzione ).
Martijn Pieters

1
Installa jq su OSX con brew install jq
Andy Fraley,

1
curl -sè equivalente a curl --silent, mentre jq -rsignifica jq --raw-outputcioè senza virgolette.
Serge Stroobandt,

python -c "richieste di importazione; r = request.get (' api.github.com/users/lambda');print r.json () [' name '];" . Il più semplice!
NotTooTechy,

277

Per estrarre rapidamente i valori di una chiave particolare, personalmente mi piace usare "grep -o", che restituisce solo la corrispondenza della regex. Ad esempio, per ottenere il campo "testo" dai tweet, qualcosa del tipo:

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

Questa regex è più robusta di quanto si possa pensare; ad esempio, si occupa di stringhe con virgole incorporate e virgolette sfuggite al loro interno. Penso che con un po 'più di lavoro potresti realizzarne uno che è effettivamente garantito per estrarre il valore, se è atomico. (Se ha nidificazione, quindi un regex non può farlo ovviamente.)

E per ulteriori pulita (seppur mantenendo fuga originale della stringa) si può usare qualcosa come: | perl -pe 's/"text"://; s/^"//; s/",$//'. (L'ho fatto per questa analisi .)

A tutti gli odiatori che insistono che dovresti usare un vero parser JSON - sì, questo è essenziale per la correttezza, ma

  1. Per eseguire un'analisi davvero rapida, come contare i valori per controllare i bug di pulizia dei dati o avere un'idea generale dei dati, sbattere qualcosa sulla riga di comando è più veloce. Aprire un editor per scrivere una sceneggiatura è fonte di distrazione.
  2. grep -oè ordini di grandezza più veloci della jsonlibreria standard Python , almeno quando lo si fa per i tweet (che sono ~ 2 KB ciascuno). Non sono sicuro se questo è solo perché jsonè lento (dovrei confrontarmi con yajl qualche volta); ma in linea di principio, un regex dovrebbe essere più veloce poiché è stato finito e molto più ottimizzabile, invece di un parser che deve supportare la ricorsione, e in questo caso, spende un sacco di alberi di CPU per le strutture che non ti interessano. (Se qualcuno scrivesse un trasduttore di stato finito che eseguiva un corretto analisi JSON (limitata in profondità), sarebbe fantastico! Nel frattempo abbiamo "grep -o".)

Per scrivere codice gestibile, utilizzo sempre una vera libreria di analisi. Non ho provato jsawk , ma se funziona bene, si tratterebbe del punto 1.

Un'ultima soluzione più stravagante: ho scritto uno script che utilizza Python jsoned estrae le chiavi desiderate in colonne separate da tabulazioni; poi passo attraverso un wrapper awkche consente l'accesso denominato alle colonne. Qui: gli script json2tsv e tsvawk . Quindi per questo esempio sarebbe:

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

Questo approccio non si rivolge al n. 2, è più inefficiente di un singolo script Python ed è un po 'fragile: forza la normalizzazione di nuove righe e tabulazioni nei valori di stringa, per giocare bene con la visione del mondo delimitata da campo / record. Ma ti consente di rimanere sulla riga di comando, con più correttezza di grep -o.


11
Hai dimenticato i valori interi. grep -Po '"text":(\d*?,|.*?[^\\]",)'
Robert,

3
Robert: Giusto, il mio regex è stato scritto solo per valori stringa per quel campo. I numeri interi possono essere aggiunti come dici tu. Se vuoi tutti i tipi, devi fare sempre di più: booleani, null. E gli array e gli oggetti richiedono più lavoro; è possibile solo la profondità limitata, con regex standard.
Brendan OConnor,

9
1. jq .namefunziona dalla riga di comando e non richiede "l'apertura di un editor per scrivere uno script". 2. Non importa quanto velocemente il tuo regex può produrre risultati sbagliati
jfs

6
e se vuoi solo i valori, puoi semplicemente lanciarti in imbarazzo. | grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
JeffCharter,

34
Sembra che su OSX -Pmanchi l' opzione. Ho provato su OSX 10.11.5 ed è grep --versionstato grep (BSD grep) 2.5.1-FreeBSD. Ho funzionato con l'opzione "estensione regex" su OSX. Il comando dall'alto sarebbe grep -Eo '"text":.*?[^\\]",' tweets.json.
Jens,

174

Sulla base del fatto che alcune delle raccomandazioni qui riportate (in particolare nei commenti) hanno suggerito l'uso di Python, sono rimasto deluso dal non trovare un esempio.

Quindi, ecco una riga per ottenere un singolo valore da alcuni dati JSON. Presuppone che tu stia eseguendo il piping dei dati (da qualche parte) e quindi dovrebbe essere utile in un contesto di scripting.

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

Ho migliorato questa risposta qui sotto per usare una funzione bash: curl 'some_api' | getJsonVal 'key'
Joe Heyming,

pythonpy( github.com/russell91/pythonpy è quasi sempre un'alternativa migliore a python -c, anche se deve essere installato con pip. basta reindirizzare il json a py --ji -x 'x[0]["hostname"]'. Se non si desidera utilizzare il supporto json_input integrato, è ancora possibile ottenere quelli vengono importati automaticamente comepy 'json.loads(sys.stdin)[0]["hostname"]'
RussellStewart

2
Grazie! Per un analisi JSON più veloce e sporca l'ho avvolto in una funzione bash: in jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); print($1)"; }modo che potessi scrivere: curl ...... | jsonq 'json.dumps([key["token"] for key in obj], indent=2)'e più di cose spaventose simili ... A proposito, obj[0]sembra inutile, sembra che funzioni objbene nei casi predefiniti (?).
Akavel,

Grazie. Ho reso questo rispetto JSON un po 'meglio della stampa:jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); sys.stdout.write(json.dumps($1))"; }
Adam K Dean

4
obj[0]causa un errore durante l'analisi { "port":5555 }. Funziona bene dopo la rimozione [0].
CyberEd

134

Seguendo l'esempio di MartinR e Boecko:

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

Questo ti darà un risultato estremamente amichevole. Molto conveniente:

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

37
Come estrarresti una chiave specifica, come sta chiedendo OP?
juan,

2
La migliore risposta finora imho, non è necessario installare nient'altro sulla maggior parte delle distribuzioni e puoi farlo | grep field. Grazie!
Andrea Richiardi,

7
Tutto ciò che fa è formattare il JSON, se non sbaglio. Non consente al chiamante di selezionare un determinato campo dall'output, come farebbe una soluzione xpath o qualcosa basato su "Puntatore JSON".
Cheeso,

4
Ho appena finito con una coppia chiave-valore, ma non il valore in sé e per sé.
christopher,

1
jqin genere non è installato mentre lo è python. Inoltre, una volta che sei in Python potresti anche andare per tutto il percorso e analizzarlo conimport json...
CpILL

125

Potresti semplicemente scaricare jqbinario per la tua piattaforma ed eseguire ( chmod +x jq):

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

Estrae l' "name"attributo dall'oggetto json.

jqhomepage dice che è come sedper i dati JSON.


27
Solo per la cronaca, jqè uno strumento straordinario.
hoss il

2
Concordato. Non posso confrontare con jsawk dalla risposta accettata, poiché non l'ho usato, ma per la sperimentazione locale (dove l'installazione di uno strumento è accettabile) consiglio vivamente jq. Ecco un esempio leggermente più ampio, che prende ogni elemento di un array e sintetizza un nuovo oggetto JSON con i dati selezionati: curl -s https://api.example.com/jobs | jq '.jobs[] | {id, o: .owner.username, dateCreated, s: .status.state}'
jbyler,

2
Ama questo. Molto leggero, e dato che è nella vecchia C, può essere compilato praticamente ovunque.
Benmj,

1
Il più pratico: non ha bisogno di librerie di terze parti (mentre jsawk lo fa) ed è facile da installare (OSX: brew install jq)
lauhub

1
Questa è la risposta più pratica e facilmente implementabile per il mio caso d'uso. Per il sistema Ubuntu (14.04) un semplice jq apt-get install ha aggiunto lo strumento al mio sistema. Sto eseguendo il piping dell'output JSON dalle risposte della CLI di AWS in jq e funziona benissimo per estrarre i valori da alcune chiavi nidificate nella risposta.
Brandon K,

105

Utilizzando Node.js

Se il sistema ha installato, è possibile utilizzare i flag di script -pprint ed -eevaulate JSON.parseper estrarre qualsiasi valore necessario.

Un semplice esempio usando la stringa JSON { "foo": "bar" }ed estraendo il valore di "pippo":

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

Poiché abbiamo accesso catae altre utilità, possiamo usarlo per i file:

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

O qualsiasi altro formato come un URL che contiene JSON:

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

1
Grazie! ma nel mio caso funziona solo con -e flagnode -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
questa è la mia soluzione preferita; utilizzare un linguaggio (javascript) per analizzare una struttura di dati naturale (JSON). sembra il più corretto . inoltre - probabilmente il nodo è già disponibile sul sistema e non dovrai manipolare i binari di jq (che sembra un'altra scelta corretta ).
Eliran Malka

Questa è la funzione di script bash: # jsonv ottiene il valore dell'oggetto json per uno specifico attributo # primo parametro è il documento json # secondo parametro è l'attributo il cui valore deve essere restituito get_json_attribute_value () {node -pe 'JSON.parse (process. argv [1]) [process.argv [2]] '"$ 1" "$ 2"}
Youness

6
Quanto segue funziona con Node.js 10:cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
Ilya Boyandin,

100

Usa il supporto JSON di Python invece di usare awk!

Qualcosa come questo:

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

6
Scusatemi per aver cercato di ottenere una buona risposta ...: proverò di più. Partisanship richiede molto più che scrivere una sceneggiatura originale per scrollarsela di dosso!
martinr,

9
Perché usi la variabile obj in quella soluzione oneliner? È inutile e non viene comunque archiviato? Si scrive meno utilizzando json.load(sys.stdin)['"key']"come esempio, come: curl -sL httpbin.org/ip | python -c "import json,sys; print json.load(sys.stdin)['origin']".
m3nda,

65

Hai chiesto come spararti al piede e sono qui per fornire le munizioni:

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

È possibile utilizzare tr -d '{}'invece di sed. Ma lasciarli completamente fuori sembra avere anche l'effetto desiderato.

Se si desidera eliminare le virgolette esterne, reindirizzare il risultato di cui sopra sed 's/\(^"\|"$\)//g'

Penso che altri abbiano suonato un allarme sufficiente. Starò con un telefono cellulare per chiamare un'ambulanza. Spara quando sei pronto.


10
In questo modo sta la follia, leggi questo: stackoverflow.com/questions/1732348/…
In pausa fino a nuovo avviso.

3
Ho letto tutte le risposte e questa funziona perfettamente per me senza dipendenze extra. +1
eth0,

Questo è quello che stavo cercando. L'unica correzione - fornita dal comando sed per rimuovere le virgolette non ha funzionato per me, ho usato invece sed 's / "// g'
AlexG

44

Utilizzo di Bash con Python

Crea una funzione bash nel tuo file .bash_rc

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

Poi

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

Ecco la stessa funzione, ma con controllo degli errori.

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

Dove $ # -ne 1 si assicura almeno 1 input e -t 0 si assicura che si stia reindirizzando da una pipe.

La cosa bella di questa implementazione è che puoi accedere ai valori json nidificati e ottenere json in cambio! =)

Esempio:

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

Se vuoi essere davvero fantasioso, potresti piuttosto stampare i dati:

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 senza la funzione 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 vuoi che funzioni sia con Python 2 che con 3.
Per Johansson il

Sto pensando che dovrebbe cambiare in system.stdout.write (obj $ 1). In questo modo puoi dire: getJsonVal "['environment'] ['name']", come nell'esempio di @Cheeso
Joe Heyming,

1
@Narek In tal caso, sembrerebbe così: funzionegetJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
Joe Heyming,

30

TickTick è un parser JSON scritto in bash (<250 righe di codice)

Ecco lo snippit dell'autore dal suo articolo, Immagina un mondo in cui Bash supporta 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
Essendo l'unica robusta risposta pure-bash qui, questo merita più voti.
Ed Randall,

C'è un modo per stampare di nuovo questa variabile people in una stringa json? Sarebbe estremamente utile
Thomas Fournet,

1
Finalmente una risposta che non consiglia Python o altri metodi atroci ... Grazie!
Akito

21

Analisi di JSON con CLI PHP

Probabilmente fuori tema, ma poiché regna la precedenza questa domanda rimane incompleta senza menzionare il nostro fidato e fedele PHP, ho ragione?

Utilizzando lo stesso esempio JSON, ma consente di assegnarlo a una variabile per ridurre l'oscurità.

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

Ora per la bontà di PHP, usando file_get_contents e il wrapper stream php: // stdin .

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

o come sottolineato usando i budget e il flusso già aperto nella costante CLI STDIN .

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

nJoy!


Puoi anche usare $argninvece difgets(STDIN)
IcanDivideBy0

Ops, $argnfunziona con il flag -E o -R e solo se il contenuto JSON è su una riga ...
IcanDivideBy0

21

Versione nativa di Bash: funziona bene anche con barre rovesciate (\) e virgolette (")

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

Questo è bellissimo. Ma se la stringa JSON contiene più di una chiave e-mail, il parser restituirà john@doe.com "" john@doe.com
rtc11

Non funziona se c'è un trattino nell'e-mail come jean-pierre@email.com
alexmngn

13

Versione che utilizza Ruby e http://flori.github.com/json/

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

o più concisamente:

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

3
questo è il mio preferito;) A proposito, puoi abbreviarlo con ruby ​​-rjson per richiedere la libreria
lucapette,

Si noti che il finale ;non è richiesto in Ruby (viene utilizzato solo per concatenare istruzioni che normalmente si troverebbero su righe separate in un'unica riga).
Zack Morris,

11

Sfortunatamente la risposta più votata che utilizza greprestituisce la corrispondenza completa che non ha funzionato nel mio scenario, ma se sai che il formato JSON rimarrà costante puoi usare lookbehind e lookahead per estrarre solo i valori desiderati.

# 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

In realtà non si conosce mai l'ordine degli elementi in un dizionario JSON. Sono, per definizione, non ordinati. Questo è precisamente uno dei motivi fondamentali per cui eseguire il rollup del proprio parser JSON è un approccio condannato.
Tripleee,

10

Se qualcuno vuole semplicemente estrarre valori da semplici oggetti JSON senza la necessità di strutture nidificate, è possibile usare espressioni regolari senza nemmeno lasciare la bash.

Ecco una funzione che ho definito usando espressioni regolari bash basate sullo standard 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
}

Avvertenze: gli oggetti e le matrici non sono supportati come valore, ma sono supportati tutti gli altri tipi di valore definiti nello standard. Inoltre, una coppia verrà abbinata indipendentemente dalla profondità del documento JSON purché abbia esattamente lo stesso nome chiave.

Utilizzando l'esempio di 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 possiamo estrarre i valori delle proprietà nidificate con questa funzione?
vsbehere

8

C'è un modo più semplice per ottenere una proprietà da una stringa JSON. Usando un package.jsonfile come esempio, prova questo:

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

Lo stiamo usando process.envperché questo porta i contenuti del file in node.js come una stringa senza il rischio che i contenuti dannosi sfuggano al loro preventivo e vengano analizzati come codice.


L'uso della concatenazione di stringhe per sostituire i valori in una stringa analizzata come codice consente l'esecuzione del codice node.js arbitrario, il che significa che è estremamente pericoloso da utilizzare con contenuti casuali che hai ottenuto da Internet. C'è un motivo per cui i modi sicuri / migliori per analizzare JSON in JavaScript non si limitano a valutarlo.
Charles Duffy,

@CharlesDuffy non sono sicuro di seguirlo, ma la chiamata JSON.parse dovrebbe essere più sicura, poiché require()può effettivamente eseguire codice esterno, JSON.parse non può.
Alexander Mills,

Questo è vero se e solo se la tua stringa viene effettivamente iniettata nel runtime JSON in modo da bypassare il parser. Non vedo il codice qui farlo in modo affidabile. Estrailo da una variabile d'ambiente e passalo a JSON.parse()e sì, sei inequivocabilmente sicuro ... ma qui, il runtime JSON sta ricevendo il contenuto (non attendibile) in banda con il codice (fidato).
Charles Duffy,

... allo stesso modo, se il tuo codice legge il file JSON da un file come una stringa e passa quella stringa JSON.parse(), sei al sicuro anche allora, ma non sta succedendo neanche qui.
Charles Duffy,

1
... ah, diamine, potrebbe anche andare nel "come" immediatamente. Il problema è che stai sostituendo la variabile shell, a cui intendi essere passato JSON.parse(), nel codice . Stai assumendo che mettere backtick letterali manterrà il contenuto letterale, ma questo è un presupposto completamente non sicuro, perché i backtick letterali possono esistere nel contenuto del file (e quindi nella variabile), e quindi possono terminare la citazione e entrare in un contesto non quotato in cui il i valori vengono eseguiti come codice.
Charles Duffy,

7

Ora che Powershell è multipiattaforma, ho pensato di farmi strada là fuori, dal momento che trovo che sia abbastanza intuitivo ed estremamente semplice.

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

ConvertFrom-Json converte JSON in un oggetto personalizzato Powershell, in modo da poter lavorare facilmente con le proprietà da quel punto in avanti. Se, ad esempio, si desidera solo la proprietà "id", è sufficiente:

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

Se volessi invocare il tutto dall'interno di Bash, allora dovresti chiamarlo così:

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

Naturalmente c'è un modo puro Powershell per farlo senza arricciatura, che sarebbe:

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

Infine, c'è anche 'ConvertTo-Json' che converte un oggetto personalizzato in JSON altrettanto facilmente. Ecco un esempio:

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

Che produrrebbe un bel JSON in questo modo:

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

}

Certo, usare una shell di Windows su Unix è un po 'sacrilego, ma Powershell è davvero bravo in alcune cose, e l'analisi di JSON e XML ne è un paio. Questa è la pagina GitHub per la versione multipiattaforma https://github.com/PowerShell/PowerShell


votato perché stai promuovendo la nuova strategia Microsoft per open-source i loro strumenti e incorporare strumenti stranieri open-source. È una buona cosa per il nostro mondo.
Alex,

Non mi piaceva PowerShell, ma devo ammettere che la gestione di JSON è piuttosto piacevole.
MartinThé,

6

Qualcuno che ha anche file XML, potrebbe voler guardare il mio Xidel . È un processore JSONiq cli, privo di dipendenze . (ovvero supporta anche XQuery per l'elaborazione xml o json)

L'esempio nella domanda sarebbe:

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

O con la mia sintassi di estensione non standard:

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

1
O più semplice al giorno d'oggi: xidel -s https://api.github.com/users/lambda -e 'name'(o -e '$json/name', o -e '($json).name').
Reino,

6

Non posso usare nessuna delle risposte qui. Nessun jq disponibile, nessun array di shell, nessuna dichiarazione, nessun grep -P, nessun lookbehind e lookahead, nessun Python, nessun Perl, nessun Ruby, no - nemmeno Bash ... Le restanti risposte semplicemente non funzionano bene. JavaScript sembrava familiare, ma la scatola dice Nescaffe - quindi è un non andare anche :) :) Anche se disponibili, per la mia semplice necessità - sarebbero eccessivi e lenti.

Tuttavia, è estremamente importante per me ottenere molte variabili dalla risposta in formato json del mio modem. Lo sto facendo in un attimo con BusyBox molto ridotto sui miei router! Nessun problema usando solo awk: basta impostare i delimitatori e leggere i dati. Per una singola variabile, tutto qui!

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

Ricordi che non ho array? Ho dovuto assegnare all'interno dei dati analizzati awk alle 11 variabili di cui ho bisogno in uno script di shell. Ovunque guardassi, si diceva che fosse una missione impossibile. Nessun problema anche con quello.

La mia soluzione è semplice Questo codice: 1) analizzerà il file .json dalla domanda (in realtà, ho preso in prestito un campione di dati di lavoro dalla risposta più votata) e selezionerà i dati citati, più 2) creerà le variabili della shell all'interno del awk assegnando la shell denominata libera nomi di variabili.

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"

Nessun problema con gli spazi vuoti all'interno. Nel mio uso, lo stesso comando analizza un output a riga singola lunga. Poiché viene utilizzato eval, questa soluzione è adatta solo per dati affidabili. È semplice adattarlo per raccogliere dati non quotati. Per un numero enorme di variabili, il guadagno di velocità marginale può essere ottenuto usando altro se. La mancanza di array ovviamente significa: nessun record multiplo senza armeggiare ulteriormente. Ma laddove sono disponibili array, l'adattamento di questa soluzione è un compito semplice.

@maikel sed answer funziona quasi (ma non posso commentarlo). Per i miei dati ben formattati, funziona. Non tanto con l'esempio usato qui (le virgolette mancanti lo scaricano). È complicato e difficile da modificare. Inoltre, non mi piace dover effettuare 11 chiamate per estrarre 11 variabili. Perché? Ho cronometrato 100 loop estraendo 9 variabili: la funzione sed ha richiesto 48,99 secondi e la mia soluzione ha impiegato 0,91 secondi! Non è giusto? Eseguendo una sola estrazione di 9 variabili: 0,51 vs. 0,02 sec.


5

Puoi provare qualcosa del genere -

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

5

Puoi usare jshon:

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

Il sito dice: "Due volte più veloce, 1/6 della memoria" ... e poi: "Jshon analizza, legge e crea JSON. È progettato per essere il più utilizzabile possibile all'interno della shell e sostituisce fragili parser ad hoc fatti da grep / sed / awk e parser di una riga di peso elevato realizzati in perl / pitone. "
Roger,

questa è elencata come la soluzione consigliata per l'analisi di JSON in Bash
qodeninja

qual è il modo più semplice per sbarazzarsi delle virgolette intorno al risultato?
gMale

4

ecco un modo in cui puoi farlo 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
        }
    }
}'

4

Per analisi JSON più complesse suggerisco di usare il modulo jsonpath di Python (di Stefan Goessner) -

  1. Installalo -

sudo easy_install -U jsonpath

  1. Usalo -

Esempio file.json (da 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
    }
  }
}

Analizzalo (estrai tutti i titoli di libri con un prezzo <10) -

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

Verrà emesso -

Sayings of the Century
Moby Dick

NOTA: la riga di comando sopra non include il controllo degli errori. per una soluzione completa con controllo degli errori è necessario creare un piccolo script Python e racchiudere il codice con try-tranne.


bellissimo linguaggio. Non conosco nemmeno Python, ma questa sembra una soluzione potente
Sridhar Sarnobat,

Ho avuto un po 'di problemi a installare l'installazione jsonpathcosì installata jsonpath_rw, quindi ecco qualcosa di simile che puoi provare se quanto sopra non funziona: 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;"(Ho usato il percorso completo del binario di Python perché avevo problemi con più pitoni installato).
Sridhar Sarnobat,

4

Se hai php :

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

Ad esempio:
abbiamo una risorsa che fornisce a json i codici iso dei paesi: http://country.io/iso3.json e possiamo facilmente vederlo in una shell con curl:

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

ma non sembra molto conveniente, e non leggibile, meglio analizzare json e vedere la struttura leggibile:

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

Questo codice stamperà qualcosa del tipo:

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

se hai matrici nidificate questo output avrà un aspetto molto migliore ...

Spero che questo sia utile ...


4

Esiste anche uno strumento di elaborazione CLI JSON molto semplice ma potente fx - https://github.com/antonmedv/fx

Esempio di formattazione JSON nel terminale Bash

Esempi

Usa la funzione anonima:

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

Se non si passa alla funzione anonima param => ..., il codice verrà automaticamente trasformato in funzione anonima. E puoi ottenere l'accesso a JSON con questa parola chiave:

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

Oppure usa anche la sintassi del punto:

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

È possibile passare un numero qualsiasi di funzioni anonime per ridurre JSON:

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

È possibile aggiornare JSON esistente utilizzando l'operatore spread:

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

Semplicemente JavaScript . Non è necessario imparare una nuova sintassi.


AGGIORNAMENTO 2018-11-06

fxora ha la modalità interattiva ( ! )

https://github.com/antonmedv/fx


7
Se stai promuovendo la tua creazione, devi essere esplicito al riguardo. Vedi Come non essere uno spammer.
triplo

4

Questo è un altro bashe pythonrisposta ibrido. Ho pubblicato questa risposta perché volevo elaborare un output JSON più complesso, ma riducendo la complessità della mia applicazione bash. Voglio aprire il seguente oggetto JSON da http://www.arcgis.com/sharing/rest/info?f=json in bash:

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

Nel seguente esempio, ho creato la mia implementazione jqe unquotesfruttamento python. Noterai che una volta importato l'oggetto Python da jsonun dizionario Python, possiamo usare la sintassi di Python per navigare nel dizionario. Per navigare sopra, la sintassi è:

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

Usando magic in bash, omettiamo datae forniamo il testo di Python solo a destra dei dati, ad es

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

Nota, senza parametri, jqfunge da prettificatore JSON. Con i parametri possiamo usare la sintassi di Python per estrarre dal dizionario tutto ciò che desideriamo, inclusi i dizionari di navigazione e gli elementi dell'array.

Ecco un esempio funzionante che dimostra quanto sopra:

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

Ho fatto questo, "analizzando" una risposta json per un valore particolare, come segue:

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

Chiaramente, $ url qui sarebbe l'url di Twitter, e $ var sarebbe "text" per ottenere la risposta per quel var.

Davvero, penso che l'unica cosa che sto facendo l'OP abbia lasciato fuori sia grep per la linea con la variabile specifica che cerca. Awk afferra il secondo oggetto sulla linea e con sed spoglio le virgolette.

Qualcuno più intelligente di me potrebbe probabilmente pensare tutto con awk o grep.

Ora, potresti fare tutto con solo sed:

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

quindi, no awk, no grep ... Non so perché non ci avevo pensato prima. Hmmm ...


In realtà, con sed puoi farlo
tonybaldwin il

1
Le condotte grep | awk | sede sed | sed | sedsono antipattern dispendiosi. Il tuo ultimo esempio può essere facilmente riscritto, curl "$url" | sed '/text/!d;s/\"text\"://g;s/\"//g;s/\ //g'ma come altri hanno sottolineato, questo è un approccio soggetto a errori e fragile che non dovrebbe essere raccomandato in primo luogo.
Tripleee,

Ho dovuto usare grep -oPz 'name \ ": \". *? \ "' Curloutput | sed 's / name \": / \ n / g'
Ferroao

3

L'analisi di JSON è dolorosa in uno script di shell. Con un linguaggio più appropriato, creare uno strumento che estrae gli attributi JSON in modo coerente con le convenzioni di shell scripting. È possibile utilizzare il nuovo strumento per risolvere il problema immediato di scripting della shell e quindi aggiungerlo al kit per situazioni future.

Ad esempio, considera uno strumento jsonlookup tale che se dico jsonlookup access token idrestituirà l' ID attributo definito all'interno del token dell'attributo definito all'interno dell'accesso all'attributo da stdin, che è presumibilmente dati JSON. Se l'attributo non esiste, lo strumento non restituisce nulla (stato di uscita 1). Se l'analisi non riesce, uscire dallo stato 2 e un messaggio a stderr. Se la ricerca ha esito positivo, lo strumento stampa il valore dell'attributo.

Dopo aver creato uno strumento unix allo scopo preciso di estrarre valori JSON, è possibile utilizzarlo facilmente negli script di shell:

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

Qualsiasi linguaggio farà per l'implementazione di jsonlookup . Ecco una versione abbastanza concisa di Python:

#!/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 liner che usa il pitone. Funziona particolarmente bene se stai scrivendo un singolo file .sh e non vuoi dipendere da un altro file .py. Sfrutta anche l'uso del tubo |. echo "{\"field\": \"value\"}"può essere sostituito da qualsiasi cosa stia stampando un json sullo stdout.

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

La domanda non era alla ricerca di una soluzione Python. Vedi anche i commenti.
Andrew Barber,

3

Questo è un buon esempio per pythonpy :

curl 'http://twitter.com/users/username.json' | py 'json.load(sys.stdin)["name"]'

Ancora più breve, il modulo Python -c qui :) bello.
m3nda,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.