Analiza JSON za pomocą narzędzi uniksowych


878

Próbuję przeanalizować JSON zwrócony z żądania curl, tak:

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

Powyższe dzieli JSON na pola, na przykład:

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

Jak wydrukować określone pole (oznaczone przez -v k=text)?


5
Erm, który nie jest dobry parsowania jsona btw ... co ze znakami ucieczki w ciągach ... itd. CZY na SO jest odpowiedź python (nawet odpowiedź perla ...)?
martinr

51
Za każdym razem, gdy ktoś mówi „problem X można łatwo rozwiązać za pomocą innego języka Y”, to jest kod dla „mojego zestawu narzędzi ma tylko kamień do wbijania gwoździ ... po co zawracać sobie głowę czymkolwiek innym?”
BryanH

22
@BryanH: z wyjątkiem tego, że czasami język Y może być lepiej przygotowany do rozwiązania konkretnego problemu X bez względu na to, ile języków zna osoba, która zaproponowała Y.
jfs

15
Trochę późno, ale proszę bardzo. grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json. To rozwiązuje zadanie łatwo i tylko z grep i działa idealnie dla prostych JSONów. W przypadku złożonych JSON-ów powinieneś użyć odpowiedniego parsera.
diosney

2
@auser, czy byłbyś w porządku z edycją zmieniającą „z sed i awk” na „z narzędziami UNIX” w tytule?
Charles Duffy

Odpowiedzi:


1126

Istnieje wiele narzędzi zaprojektowanych specjalnie do manipulowania JSON z poziomu wiersza poleceń i będzie o wiele łatwiejsze i bardziej niezawodne niż robienie tego z Awk, takie jak jq:

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

Można to również zrobić za pomocą narzędzi, które prawdopodobnie są już zainstalowane w systemie, takich jak Python korzystający z jsonmodułu , dzięki czemu można uniknąć dodatkowych zależności, a jednocześnie korzystać z właściwego parsera JSON. Poniżej założono, że chcesz użyć UTF-8, w którym powinien być zakodowany oryginalny JSON i jest to również to, czego używają najbardziej nowoczesne terminale:

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

Notatki historyczne

Ta odpowiedź początkowo zalecała jsawk , który powinien nadal działać, ale jest nieco bardziej kłopotliwy w użyciu niż jqi zależy od zainstalowanego samodzielnego interpretera JavaScript, który jest mniej powszechny niż interpreter Pythona, więc powyższe odpowiedzi są prawdopodobnie preferowane:

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

Ta odpowiedź również pierwotnie używała interfejsu API Twittera z pytania, ale ten interfejs API już nie działa, co utrudnia skopiowanie przykładów do przetestowania, a nowy interfejs API Twittera wymaga kluczy API, więc przełączyłem się na używanie interfejsu API GitHub, który może być łatwo używany bez kluczy API. Pierwsza odpowiedź na pierwotne pytanie brzmiałaby:

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

7
@thrau +1. jq jest dostępny w repozytorium i bardzo łatwy w użyciu, więc jest znacznie lepszy niż jsawk. Testowałem oba przez kilka minut, jq wygrał tę bitwę
Szymon Sadło

1
Zauważ, że w Pythonie 2, jeśli przekazujesz dane wyjściowe do innej komendy, wówczas printinstrukcja zawsze będzie kodować do ASCII, ponieważ używasz Pythona w potoku. Wstaw PYTHONIOENCODING=<desired codec>do polecenia, aby ustawić inne kodowanie wyjściowe, odpowiednie dla twojego terminala. W Pythonie 3 wartością domyślną jest UTF-8 w tym przypadku (przy użyciu print() funkcji ).
Martijn Pieters

1
Zainstaluj jq na OSX z instalacją brew jq
Andy Fraley,

1
curl -sjest równoważne curl --silent, podczas gdy jq -rznaczy to jq --raw-outputznaczy bez cudzysłowów łańcuchowych.
Serge Stroobandt,

python -c "żądania importu; r = requests.get (' api.github.com/users/lambda');print r.json () [' name '];" . Najprostszy!
NotTooTechy

276

Aby szybko wyodrębnić wartości dla konkretnego klucza, osobiście lubię używać „grep -o”, który zwraca tylko dopasowanie wyrażenia regularnego. Na przykład, aby uzyskać pole „tekstowe” z tweetów, coś takiego:

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

Ten wyrażenie regularne jest bardziej niezawodne, niż mogłoby się wydawać; na przykład dobrze radzi sobie z ciągami znaków z osadzonymi przecinkami i znakami cudzysłowu. Myślę, że przy odrobinie pracy można stworzyć taki, który w rzeczywistości gwarantuje wydobycie wartości, jeśli jest ona atomowa. (Jeśli ma zagnieżdżenie, wyrażenie regularne nie może tego oczywiście zrobić).

I do dalszego czysty (choć zachowując oryginalny ucieczce struny) można użyć coś takiego: | perl -pe 's/"text"://; s/^"//; s/",$//'. (Zrobiłem to dla tej analizy .)

Wszystkim hejterom, którzy nalegają, powinieneś użyć prawdziwego parsera JSON - tak, jest to niezbędne dla poprawności, ale

  1. Aby dokonać naprawdę szybkiej analizy, na przykład zliczania wartości w celu sprawdzenia błędów czyszczenia danych lub uzyskania ogólnego wyczucia danych, wybijanie czegoś w wierszu poleceń jest szybsze. Otwieranie edytora w celu napisania skryptu jest rozpraszające.
  2. grep -ojest o rząd wielkości szybszy niż standardowa jsonbiblioteka Pythona , przynajmniej w przypadku tweetów ( o wielkości ~ 2 KB każda). Nie jestem pewien, czy jsondzieje się tak tylko dlatego, że jest powolny (powinienem kiedyś porównać do yajla); ale w zasadzie wyrażenie regularne powinno być szybsze, ponieważ jest w stanie skończonym i jest o wiele bardziej możliwe do zoptymalizowania, zamiast parsera, który musi obsługiwać rekurencję, iw tym przypadku spędza wiele drzew budujących procesor dla struktur, na których ci nie zależy. (Gdyby ktoś napisał skończony przetwornik stanu, który poprawnie parsował JSON (z ograniczoną głębokością), byłoby fantastycznie! W międzyczasie mamy „grep -o”.)

Aby pisać możliwy do utrzymania kod, zawsze używam prawdziwej biblioteki parsującej. Nie próbowałem jsawk , ale jeśli działa dobrze, to adresowałby punkt 1.

Ostatnie, zwięzłe rozwiązanie: napisałem skrypt, który używa Pythona jsoni wyodrębnia klucze, które chcesz, do kolumn oddzielonych tabulatorami; następnie przepuszczam rurkę przez opakowanie, awkktóre umożliwia nazwany dostęp do kolumn. Tutaj: skrypty json2tsv i tsvawk . W tym przykładzie byłoby to:

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

Podejście to nie odnosi się do nr 2, jest bardziej nieefektywne niż pojedynczy skrypt Pythona i jest trochę kruche: wymusza normalizację znaków nowej linii i tabulatorów w wartościach ciągów, aby dobrze się bawić z widokiem świata ograniczonym polem / rekordem awk. Ale pozwala ci pozostać w wierszu poleceń, z większą poprawnością niż grep -o.


11
Zapomniałeś o wartościach całkowitych. grep -Po '"text":(\d*?,|.*?[^\\]",)'
Robert

3
Robert: Racja, moje wyrażenie regularne zostało napisane tylko dla wartości ciągu dla tego pola. Liczby całkowite mogą być dodawane, jak mówisz. Jeśli chcesz wszystkich typów, musisz robić coraz więcej: booleany, null. A tablice i obiekty wymagają więcej pracy; możliwe jest tylko ograniczenie głębokości, przy standardowych wyrażeniach regularnych.
Brendan OConnor,

9
1. jq .namedziała w wierszu poleceń i nie wymaga „otwierania edytora, aby napisać skrypt”. 2. Nie ma znaczenia, jak szybko wyrażenie regularne może dawać błędne wyniki
jfs

6
a jeśli chcesz tylko wartości, możesz po prostu rzucić na to awk. | grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
JeffCharter,

34
Wygląda na to, że w OSX tej -Popcji brakuje. Testowałem na OSX 10.11.5 i tak grep --versionbyło grep (BSD grep) 2.5.1-FreeBSD. Mam to działa z opcją „Extended regex” w OSX. Polecenie z góry byłoby grep -Eo '"text":.*?[^\\]",' tweets.json.
Jens

174

Na podstawie tego, że niektóre z rekomendacji (szczególnie w komentarzach) sugerowały użycie Pythona, byłem rozczarowany, że nie znalazłem przykładu.

Oto jeden linijka, aby uzyskać jedną wartość z niektórych danych JSON. Zakłada się, że przesyłasz dane skądś (skądś) i dlatego powinno być przydatne w kontekście skryptów.

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

Ulepszyłem tę odpowiedź poniżej, aby użyć funkcji bash: curl 'some_api' | getJsonVal 'key'
Joe Heyming

pythonpy( github.com/russell91/pythonpy jest prawie zawsze lepszą alternatywą dla python -c, chociaż trzeba go zainstalować za pomocą pip. po prostu py --ji -x 'x[0]["hostname"]'potokuj json . Jeśli nie chcesz używać wbudowanej obsługi json_input, nadal możesz uzyskać te są importowane automatycznie jakopy 'json.loads(sys.stdin)[0]["hostname"]'
RussellStewart

2
Dzięki! Aby uzyskać szybsze i brudne parsowanie JSON, zawinąłem go w funkcję bash: jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); print($1)"; }aby móc pisać: curl ...... | jsonq 'json.dumps([key["token"] for key in obj], indent=2)'i więcej podobnych przerażających rzeczy ... Btw, obj[0]wydaje się niepotrzebne, wygląda na to, że objdziała po prostu w domyślnych przypadkach (?).
akavel

Dzięki. Uczyniłem ten szacunek JSON trochę lepszym niż drukiem:jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); sys.stdout.write(json.dumps($1))"; }
Adam K Dean

4
obj[0]powoduje błąd podczas analizowania { "port":5555 }. Działa dobrze po usunięciu [0].
CyberEd

134

Podążając za MartinR i Boecko:

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

To da ci wyjątkowo przyjazny efekt grep. Bardzo wygodne:

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

37
Jak wyodrębnić konkretny klucz, o co prosi OP?
juan

2
Najlepsza odpowiedź do tej pory, imho, nie musisz instalować niczego innego na większości dystrybucji i możesz | grep field. Dzięki!
Andrea Richiardi,

7
Wszystko to robi formatowanie JSON, jeśli się nie mylę. Nie pozwala dzwoniącemu na wybranie określonego pola z wyniku, podobnie jak rozwiązanie xpath lub coś opartego na „JSON Pointer”.
Cheeso

4
Po prostu mam parę kluczowych wartości, ale nie samą w sobie.
christopher

1
jqnie jest zwykle instalowany, gdy Python jest. Ponadto, gdy jesteś już w Pythonie, możesz równie dobrze przejść całą drogę i import json...
parsować

125

Możesz po prostu pobrać plik jqbinarny na swoją platformę i uruchomić ( chmod +x jq):

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

Wyodrębnia "name"atrybut z obiektu json.

jqstrona główna mówi, że jest tak jak w sedprzypadku danych JSON.


27
Dla przypomnienia jqjest niesamowitym narzędziem.
hoss

2
Zgoda. Nie mogę porównać z jsawk z zaakceptowanej odpowiedzi, ponieważ jej nie użyłem, ale do lokalnych eksperymentów (gdzie instalacja narzędzia jest dopuszczalna) bardzo polecam jq. Oto nieco bardziej obszerny przykład, który bierze każdy element tablicy i syntetyzuje nowy obiekt JSON z wybranymi danymi: curl -s https://api.example.com/jobs | jq '.jobs[] | {id, o: .owner.username, dateCreated, s: .status.state}'
jbyler 21.04.2014

2
Kocham to. Bardzo lekki, a ponieważ jest w zwykłym starym C, można go skompilować praktycznie w dowolnym miejscu.
Benmj

1
Najbardziej praktyczny: nie potrzebuje bibliotek stron trzecich (podczas gdy jsawk robi) i jest łatwy w instalacji (OSX: brew install jq)
lauhub

1
To jest najbardziej praktyczna i łatwa do wdrożenia odpowiedź dla mojego przypadku użycia. W systemie Ubuntu (14.04) proste narzędzie instalacyjne apt-get jq dodało narzędzie do mojego systemu. Przesyłam dane wyjściowe JSON z odpowiedzi AWS CLI do jq i działa świetnie, aby wyodrębnić wartości do niektórych kluczy zagnieżdżonych w odpowiedzi.
Brandon K

105

Korzystanie z Node.js

Jeśli system ma Po zainstalowaniu można użyć flagi -pdrukowania i -eewaluacji skryptów, JSON.parseaby wyciągnąć dowolną potrzebną wartość.

Prosty przykład z wykorzystaniem ciągu JSON { "foo": "bar" }i wyciągnięcia wartości „foo”:

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

Ponieważ mamy dostęp cati inne narzędzia, możemy użyć tego do plików:

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

Lub dowolny inny format, taki jak adres URL zawierający JSON:

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

1
dzięki! ale w moim przypadku działa tylko z flagą -enode -p -e 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
Rnd_d

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

4
to jest moje ulubione rozwiązanie; użyj języka (javascript), aby przeanalizować naturalną dla niego strukturę danych (JSON). wydaje się najbardziej poprawny . także - węzeł jest już prawdopodobnie dostępny w systemie i nie będziesz musiał mieszać się z plikami binarnymi jq (co wygląda jak kolejny poprawny wybór).
Eliran Malka

Jest to funkcja skryptu bash: # jsonv pobierz wartość obiektu json dla określonego atrybutu # pierwszy parametr to dokument json # drugi parametr to atrybut, którego wartość należy zwrócić get_json_attribute_value () {node -pe 'JSON.parse (proces. argv [1]) [process.argv [2]] '"$ 1" "$ 2"}
Youness

6
Następujące działa z Node.js 10:cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
Ilya Boyandin

100

Użyj obsługi JSON w Pythonie zamiast awk!

Coś takiego:

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

6
Wybacz mi, że próbowałem znaleźć dobrą odpowiedź ...: Postaram się bardziej. Partisanship wymaga czegoś więcej niż napisania skryptu awk, aby go strząsnąć!
martinr

9
Dlaczego używasz zmiennej obj w tym rozwiązaniu oneliner ?. Jest to bezużyteczne i w ogóle nie jest przechowywane? Piszesz mniej używając json.load(sys.stdin)['"key']"jako przykład takich jak: curl -sL httpbin.org/ip | python -c "import json,sys; print json.load(sys.stdin)['origin']".
m3nda

64

Zapytałeś, jak strzelić sobie w stopę, a ja jestem tutaj, aby dostarczyć amunicję:

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

Możesz użyć tr -d '{}'zamiast sed. Ale ich całkowite pominięcie wydaje się mieć również pożądany efekt.

Jeśli chcesz usunąć zewnętrzne cytaty, przeprowadź wynik powyższego sed 's/\(^"\|"$\)//g'

Myślę, że inni wydali wystarczający alarm. Będę stał przy telefonie komórkowym, żeby wezwać pogotowie. Strzelaj, gdy będziesz gotowy.


10
W ten sposób szaleństwo leży, przeczytaj to: stackoverflow.com/questions/1732348/...
Wstrzymane do odwołania.

3
Przeczytałem wszystkie odpowiedzi i ta działa dla mnie idealnie bez żadnych dodatkowych zależności. +1
eth0

Właśnie tego szukałem. Jedyna poprawka - pod warunkiem, że komenda sed do usuwania cudzysłowów nie działała dla mnie, zamiast tego użyłem sed 's / "// g'
AlexG

44

Używanie Bash z Pythonem

Utwórz funkcję bash w pliku .bash_rc

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

Następnie

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

Oto ta sama funkcja, ale z kontrolą błędów.

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

Gdzie $ # -ne 1 zapewnia co najmniej 1 wejście, a -t 0 upewnia się, że przekierowujesz z potoku.

Zaletą tej implementacji jest to, że możesz uzyskać dostęp do zagnieżdżonych wartości json i otrzymać je w zamian! =)

Przykład:

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

Jeśli chcesz być naprawdę fantazyjny, możesz wydrukować dane:

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 bez funkcji bash:curl http://foo | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["environment"][0]["name"]'
Cheeso

1
sys.stdout.write()jeśli chcesz, aby działał zarówno z Pythonem 2, jak i 3.
Per Johansson

Myślę, że powinien zmienić się na system.stdout.write (obj 1). W ten sposób możesz powiedzieć: getJsonVal „['środowisko'] ['nazwa']", jak przykład
@Cheeso

1
@Narek W takim przypadku wyglądałoby to tak: funkcjagetJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
Joe Heyming

30

TickTick to parser JSON napisany bash (<250 linii kodu)

Oto fragment autora z jego artykułu Wyobraź sobie świat, w którym Bash wspiera 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
Jako jedyna solidna odpowiedź typu pure-bash tutaj, zasługuje na więcej głosów pozytywnych.
Ed Randall,

Czy jest jakiś sposób, aby ponownie wydrukować zmienną people do ciągu json? Byłoby to niezwykle przydatne
Thomas Fournet

Wreszcie odpowiedź nie polecająca Pythona ani innych okropnych metod ... Dziękuję!
Akito

21

Przetwarzanie JSON z PHP CLI

Prawdopodobnie nie na temat, ale skoro panuje pierwszeństwo, to pytanie pozostaje niepełne bez wzmianki o naszym zaufanym i wiernym PHP, czy mam rację?

Korzystając z tego samego przykładu JSON, ale pozwala przypisać go do zmiennej w celu zmniejszenia niejasności.

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

Teraz na dobroć PHP, używając file_get_contents i php: // stdin stream wrapper.

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

lub jak wskazano przy użyciu fgets i już otwartego strumienia przy stałej CLI STDIN .

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

nJoy!


Możesz nawet użyć $argnzamiastfgets(STDIN)
IcanDivideBy0

Ups, $argndziała z flagą -E lub -R i tylko jeśli zawartość JSON znajduje się w jednym wierszu ...
IcanDivideBy0

21

Wersja Native Bash: Działa również dobrze z odwrotnymi ukośnikami (\) i cudzysłowami („)

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

To jest niesamowite. Ale jeśli ciąg JSON zawiera więcej niż jeden klucz e-mail, analizator składni wyśle ​​jan@doe.com „” jan@doe.com
rtc11

Nie działa, jeśli w wiadomości e-mail jest myślnik, np. Jean-pierre@email.com
alexmngn

13

Wersja korzystająca z Ruby i http://flori.github.com/json/

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

lub bardziej zwięźle:

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

3
to moja ulubiona;) BTW, można ją skrócić za pomocą ruby ​​-rjson, aby wymagać biblioteki
lucapette

Zauważ, że końcowy ;nie jest wymagany w Rubim (jest on używany tylko do konkatenacji instrukcji, które normalnie byłyby w osobnych wierszach w jednym wierszu).
Zack Morris,

11

Niestety top głosowało odpowiedź, która używa grepZwraca pełny mecz, który nie działa w moim scenariuszu, ale jeśli wiesz, format JSON pozostanie na stałym można użyć lookbehind i uprzedzona wyodrębnić tylko pożądanych wartości.

# 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

W rzeczywistości nigdy nie znasz kolejności elementów w słowniku JSON. Z definicji są nieuporządkowane. Jest to właśnie jeden z podstawowych powodów, dla których tworzenie własnego parsera JSON jest podejrzanym podejściem.
tripleee

10

Jeśli ktoś chce tylko wyodrębnić wartości z prostych obiektów JSON bez potrzeby tworzenia struktur zagnieżdżonych, możliwe jest użycie wyrażeń regularnych bez opuszczania bash.

Oto funkcja, którą zdefiniowałem za pomocą wyrażeń regularnych bash opartych na standardzie 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
}

Ostrzeżenia: obiekty i tablice nie są obsługiwane jako wartość, ale obsługiwane są wszystkie inne typy wartości zdefiniowane w standardzie. Również para zostanie dopasowana bez względu na to, jak głęboko jest w dokumencie JSON, o ile ma dokładnie taką samą nazwę klucza.

Na przykładzie 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, czy możemy wyodrębnić zagnieżdżone wartości właściwości za pomocą tej funkcji?
vsbehere

8

Jest łatwiejszy sposób na uzyskanie właściwości z ciągu Json. Korzystając z package.jsonpliku jako przykładu, spróbuj tego:

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

Używamy, process.envponieważ powoduje to umieszczenie zawartości pliku w node.js jako ciągu bez ryzyka, że ​​złośliwe treści umkną cytatom i zostaną przeanalizowane jako kod.


Używanie konkatenacji ciągów w celu podstawienia wartości w ciąg analizowany jako kod pozwala na uruchomienie dowolnego kodu node.js, co oznacza, że ​​korzystanie z losowych treści uzyskanych z Internetu jest wyjątkowo niebezpieczne. Istnieje powód, dla którego bezpieczne / sprawdzone metody analizowania JSON w JavaScript nie tylko go oceniają.
Charles Duffy

@CharlesDuffy nie jestem pewien, czy śledzę, ale wywołanie JSON.parse powinno być bezpieczniejsze, ponieważ require()faktycznie może uruchamiać obcy kod, JSON.parse nie.
Alexander Mills

Jest to prawdą, jeśli „i tylko”, jeśli łańcuch jest faktycznie wstrzykiwany do środowiska wykonawczego JSON w taki sposób, aby ominąć analizator składni. Nie widzę tutaj kodu, który robi to niezawodnie. Wyciągnij go ze zmiennej środowiskowej i przekaż do JSON.parse()i tak, jesteś jednoznacznie bezpieczny ... ale tutaj środowisko wykonawcze JSON odbiera (niezaufaną) zawartość w paśmie z (zaufanym) kodem.
Charles Duffy

... podobnie, jeśli masz kod, który odczytuje JSON z pliku jako ciąg i przekazujesz ten ciąg JSON.parse(), to również jesteś bezpieczny, ale to też się tutaj nie zdarza.
Charles Duffy

1
... ach, cholera, równie dobrze może od razu przejść do „jak”. Problem polega na tym, że zastępujesz zmienną powłoki, do której zamierzasz zostać przekazany JSON.parse(), do kodu . Jesteś przy założeniu, że wprowadzenie dosłownych odwrócone, pojedyncze apostrofy zachowa zawartość dosłownym, ale to zupełnie niebezpieczne założenie, ponieważ dosłowne backticks mogą występować w zawartości pliku (a więc zmienna), a tym samym może zakończyć cytowanie i wprowadź niecytowany kontekst gdzie wartości są wykonywane jako kod.
Charles Duffy

7

Teraz, gdy Powershell jest wieloplatformowy, pomyślałem, że tam pójdę, ponieważ uważam, że jest dość intuicyjny i niezwykle prosty.

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

ConvertFrom-Json konwertuje JSON w niestandardowy obiekt Powershell, dzięki czemu można łatwo pracować z właściwościami od tego momentu. Jeśli na przykład chcesz tylko właściwości „id”, po prostu wykonaj następujące czynności:

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

Jeśli chcesz wywołać całą rzecz z poziomu Basha, musisz nazwać to tak:

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

Oczywiście istnieje czysty sposób Powershell, aby to zrobić bez zwijania się, co byłoby:

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

Wreszcie istnieje również „ConvertTo-Json”, który równie łatwo konwertuje obiekt niestandardowy do JSON. Oto przykład:

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

Który wytworzyłby fajny JSON w ten sposób:

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

}

Trzeba przyznać, że używanie powłoki Windows na Uniksie jest nieco świętokradcze, ale Powershell jest naprawdę dobry w niektórych rzeczach, a parsowanie JSON i XML to tylko niektóre z nich. To jest strona GitHub dla wersji międzyplatformowej https://github.com/PowerShell/PowerShell


pozytywnie oceniany, ponieważ promujesz nową strategię firmy Microsoft dotyczącą open-source swoich narzędzi i włączasz zagraniczne narzędzia open source. To dobra rzecz dla naszego świata.
Alex

Kiedyś nie lubiłem PowerShell, ale muszę przyznać, że obsługa JSON jest bardzo przyjemna.
MartinThé

6

Ktoś, kto ma również pliki XML, może chcieć spojrzeć na mój Xidel . Jest to cli, niezależny procesor JSONiq . (tzn. obsługuje także XQuery dla przetwarzania XML lub JSON)

Przykładem w pytaniu byłoby:

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

Lub z moją własną, niestandardową składnią rozszerzenia:

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

1
Lub prościej w dzisiejszych czasach: xidel -s https://api.github.com/users/lambda -e 'name'(lub -e '$json/name', lub -e '($json).name').
Reino

6

Nie mogę tutaj użyć żadnej z odpowiedzi. Brak dostępnych jq, żadnych tablic powłoki, bez deklarowania, bez grep -P, bez lookbehind i lookahead, bez Pythona, bez Perla, bez Ruby, nie - nawet Bash ... Pozostałe odpowiedzi po prostu nie działają dobrze. JavaScript brzmiał znajomo, ale puszka mówi Nescaffe - więc też nie ma wyjścia :) Nawet jeśli są dostępne, dla mojej prostej potrzeby - byłyby przesadne i powolne.

Jednak niezwykle ważne jest dla mnie uzyskanie wielu zmiennych z sformatowanej w formacie json odpowiedzi mojego modemu. Robię to w mgnieniu oka z bardzo przyciętym BusyBox na moich routerach! Nie ma problemów z używaniem samego awk: wystarczy ustawić ograniczniki i odczytać dane. Dla jednej zmiennej to wszystko!

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

Pamiętasz, że nie mam tablic? Musiałem przypisać w analizowanym pliku awk 11 zmiennych, których potrzebuję w skrypcie powłoki. Gdziekolwiek spojrzałem, była to misja niemożliwa. Z tym też nie ma problemu.

Moje rozwiązanie jest proste. Ten kod: 1) parsuje plik .json z pytania (faktycznie pożyczyłem próbkę danych roboczych z najbardziej pozytywnej odpowiedzi) i wybrał cytowane dane, a także 2) tworzy zmienne powłoki z poziomu awk, przypisując dowolną nazwaną powłokę nazwy zmiennych.

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"

Brak problemów z pustymi miejscami w środku. W moim zastosowaniu to samo polecenie analizuje długi wynik jednego wiersza. Ponieważ używana jest funkcja eval, to rozwiązanie jest odpowiednie tylko dla zaufanych danych. Łatwo jest dostosować go do pobierania niecytowanych danych. W przypadku ogromnej liczby zmiennych przyrost prędkości krańcowej można osiągnąć przy użyciu else if. Brak tablic oznacza oczywiście: brak wielu rekordów bez dodatkowego majstrowania. Ale tam, gdzie dostępne są tablice, dostosowanie tego rozwiązania jest prostym zadaniem.

@maikel sed odpowiedź prawie działa (ale nie mogę tego komentować). Dla moich ładnie sformatowanych danych - działa. Nie tyle w przykładzie tutaj użytym (brakujące cytaty go odrzucają). Jest to skomplikowane i trudne do modyfikacji. Ponadto nie lubię wykonywać 11 wywołań w celu wyodrębnienia 11 zmiennych. Dlaczego? Zmierzyłem czas 100 pętli wyodrębniając 9 zmiennych: funkcja sed zajęła 48,99 sekundy, a moje rozwiązanie zajęło 0,91 sekundy! Niesprawiedliwe? Wykonywanie tylko jednej ekstrakcji 9 zmiennych: 0,51 vs. 0,02 sek.


5

Możesz spróbować czegoś takiego -

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

5

Możesz użyć jshon:

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

Witryna mówi: „Dwa razy szybciej, 1/6 pamięci” ... a następnie: „Jshon analizuje, odczytuje i tworzy JSON. Został zaprojektowany tak, aby był jak najbardziej użyteczny z poziomu powłoki i zastępuje delikatne parsery adhoc wykonane z grep / sed / awk, a także ciężkie jednowierszowe parsery wykonane z perla / pytona. ”
Roger

jest to wymienione jako zalecane rozwiązanie do analizowania JSON w Bash
qodeninja

jaki jest najłatwiejszy sposób na pozbycie się cytatów wokół wyniku?
gMale

4

Oto jeden ze sposobów, w jaki możesz to zrobić za pomocą 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

W przypadku bardziej złożonej analizy JSON sugeruję użycie modułu jsonpath w Pythonie (autor: Stefan Goessner) -

  1. Zainstaluj to -

sudo easy_install -U jsonpath

  1. Użyj tego -

Przykładowy plik.json (z 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
    }
  }
}

Parsuj (wyodrębnij wszystkie tytuły książek o cenie <10) -

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

Wyjdzie -

Sayings of the Century
Moby Dick

UWAGA: Powyższy wiersz poleceń nie obejmuje sprawdzania błędów. dla pełnego rozwiązania z kontrolą błędów powinieneś utworzyć mały skrypt Pythona i owinąć kod try-wyjątkiem.


piękny idiom. Nie znam nawet Pythona, ale wydaje się to potężnym rozwiązaniem
Sridhar Sarnobat,

Miałem trochę problemów z instalacją, jsonpathwięc zainstalowałem jsonpath_rwzamiast tego, więc tutaj jest coś podobnego, możesz spróbować, jeśli powyższe nie działa: 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;"(Użyłem pełnej ścieżki do pliku binarnego python, ponieważ miałem problemy z wieloma pytonami zainstalowany).
Sridhar Sarnobat,

4

Jeśli masz php :

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

Na przykład:
mamy zasób, który zapewnia json z kodami ISO krajów: http://country.io/iso3.json i możemy go łatwo zobaczyć w powłoce z curl:

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

ale wygląda na niezbyt wygodny i nieczytelny, lepiej parsuj json i zobacz czytelną strukturę:

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

Ten kod wyświetli coś takiego:

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

jeśli zagnieżdżono tablice, dane wyjściowe będą wyglądały znacznie lepiej ...

Mam nadzieję, że to pomoże ...


4

Istnieje również bardzo proste, ale potężne narzędzie przetwarzania JSON CLI fx - https://github.com/antonmedv/fx

Przykład formatowania JSON w terminalu Bash

Przykłady

Użyj funkcji anonimowej:

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

Jeśli nie przejdziesz funkcji anonimowej param => ..., kod zostanie automatycznie przekształcony w funkcję anonimową. Możesz uzyskać dostęp do JSON za pomocą tego słowa kluczowego:

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

Lub po prostu użyj składni kropkowej:

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

Możesz przekazać dowolną liczbę anonimowych funkcji zmniejszających JSON:

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

Możesz zaktualizować istniejący JSON za pomocą operatora spreadu:

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

Po prostu JavaScript . Nie musisz uczyć się nowej składni.


AKTUALIZACJA 2018-11-06

fxteraz ma tryb interaktywny ( ! )

https://github.com/antonmedv/fx


7
Jeśli promujesz swoje własne dzieło, musisz wyraźnie je wyrazić. Zobacz Jak nie być spamerem.
tripleee

4

To jest kolejna bashi pythonhybrydowa odpowiedź. Wysłałem tę odpowiedź, ponieważ chciałem przetwarzać bardziej złożone dane wyjściowe JSON, ale zmniejszając złożoność mojej aplikacji bash. Chcę złamać następujący obiekt JSON z http://www.arcgis.com/sharing/rest/info?f=json w bash:

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

W poniższym przykładzie utworzyłem własną implementację jqi unquotewykorzystanie python. Zauważysz, że po zaimportowaniu obiektu python jsondo słownika python możemy używać składni python do nawigacji w słowniku. Aby poruszać się po powyższym, składnia jest następująca:

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

Używając magii w bash, pomijamy datai dostarczamy tylko tekst python po prawej stronie danych, tj

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

Uwaga, bez parametrów, jq działa jako prettifier JSON. Za pomocą parametrów możemy użyć składni Pythona, aby wyodrębnić wszystko, co chcemy ze słownika, w tym nawigację w słownikach i elementach tablicy.

Oto działający przykład, który pokazuje powyższe:

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

Zrobiłem to, „analizując” odpowiedź JSON dla określonej wartości, w następujący sposób:

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

Oczywiście, tutaj $ url będzie adresem URL Twittera, a $ var będzie „tekstem”, aby uzyskać odpowiedź na ten var.

Naprawdę, myślę, że jedyną rzeczą, którą robię OP, jest pominięcie grep dla linii z konkretną zmienną, której szuka. Awk chwyta drugi przedmiot w wierszu i za pomocą sed usuwam cytaty.

Ktoś mądrzejszy ode mnie prawdopodobnie byłby w stanie przemyśleć całą sprawę za pomocą awk lub grep.

Teraz możesz to wszystko zrobić za pomocą tylko sed:

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

więc bez awk, bez grep ... Nie wiem, dlaczego wcześniej o tym nie myślałem. Hmmm...


Właściwie to z sed możesz to zrobić
tonybaldwin

1
grep | awk | sedI sed | sed | sedrurociągi są oszczędnych antipatterns. Twój ostatni przykład można łatwo przepisać, curl "$url" | sed '/text/!d;s/\"text\"://g;s/\"//g;s/\ //g'ale jak zauważyli inni, jest to podatne na błędy i kruche podejście, które nie powinno być zalecane w pierwszej kolejności.
tripleee

Musiałem użyć grep -oPz 'name \ ": \". *? \ "' Curloutput | sed 's / name \": / \ n / g'
Ferroao

3

Analiza JSON jest bolesna w skrypcie powłoki. Korzystając z bardziej odpowiedniego języka, utwórz narzędzie, które wyodrębni atrybuty JSON w sposób zgodny z konwencjami skryptowymi powłoki. Możesz użyć swojego nowego narzędzia, aby rozwiązać natychmiastowy problem ze skryptami powłoki, a następnie dodać go do zestawu na przyszłe sytuacje.

Na przykład rozważmy narzędzie jsonlookup takie, że jeśli powiem jsonlookup access token id, zwróci identyfikator atrybutu zdefiniowany w tokenie atrybutu zdefiniowanym w ramach dostępu do atrybutu ze standardowego wejścia, którym są prawdopodobnie dane JSON. Jeśli atrybut nie istnieje, narzędzie nic nie zwraca (status wyjścia 1). Jeśli parsowanie się nie powiedzie, wyjdź ze stanu 2 i wyślij wiadomość do stderr. Jeśli wyszukiwanie się powiedzie, narzędzie wydrukuje wartość atrybutu.

Po stworzeniu narzędzia uniksowego do precyzyjnego wyodrębniania wartości JSON, możesz łatwo używać go w skryptach powłoki:

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

Dowolny język zrobi dla implementacji jsonlookup . Oto dość zwięzła wersja Pythona:

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

Dwuliniowy, który używa Pythona. Działa to szczególnie dobrze, jeśli piszesz pojedynczy plik .sh i nie chcesz polegać na innym pliku .py. Wykorzystuje również użycie rury |. echo "{\"field\": \"value\"}"można zastąpić czymkolwiek wypisującym json na standardowe wyjście.

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

Pytanie nie szukało rozwiązania w języku Python. Zobacz także komentarze.
Andrew Barber,

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.