Розбір JSON за допомогою інструментів Unix


879

Я намагаюся розібрати JSON, який повернувся із запиту на завиток, як-от так:

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

Наведене вище розбиває JSON на поля, наприклад:

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

Як надрукувати певне поле (позначене символом -v k=text)?


5
Ерм, це не добре json, що розбирає btw ... що з символами втечі в рядках ... і т. Д. Є відповідь python на це на SO (Perl відповідь навіть ...)?
мартін

51
Кожен раз, коли хтось каже, що «проблему X легко вирішити іншою мовою Y», це код для «у моїй панелі інструментів є лише скеля для водіння нігтів ... навіщо турбуватися чим-небудь ще?»
BryanH

22
@BryanH: за винятком випадків, мова Y може бути більш оснащеною для вирішення конкретної задачі X незалежно від того, скільки мов знає людина, яка запропонувала Y.
jfs

15
Ніби запізнився, але ось це йде. grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json. Це вирішує завдання легко і лише з грепом і прекрасно працює для простих JSON. Для складних JSON слід використовувати належний аналізатор.
діосней

2
@auser, невже ти будеш гаразд із зміною редакції "з sed і awk" на "з інструментами UNIX" у назві?
Чарльз Даффі

Відповіді:


1127

Існує ряд інструментів, спеціально розроблених для маніпулювання JSON з командного рядка, і вони будуть набагато простішими та надійнішими, ніж робити це з Awk, такі як jq:

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

Ви також можете це зробити за допомогою інструментів, які, ймовірно, вже встановлені у вашій системі, як-от Python, що використовує jsonмодуль , і таким чином уникайте зайвих залежностей, зберігаючи при цьому належну перевагу JSON-аналізатора. Далі припускаємо, що ви хочете використовувати UTF-8, в який повинен бути закодований оригінал JSON, а також використовується більшість сучасних терміналів:

Пітон 3:

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

Пітон 2:

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

Історичні записки

Ця відповідь спочатку рекомендувала jsawk , який все ще повинен працювати, але трохи більш громіздкий у використанні, ніж jq, і залежить від встановленого окремого інтерпретатора JavaScript, який є менш поширеним, ніж інтерпретатор Python, тому наведені вище відповіді, ймовірно, бажані:

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

Ця відповідь також спочатку використовувала API Twitter у запитанні, але той API більше не працює, тому важко копіювати приклади для тестування, а для нового API API потрібні ключі API, тому я перейшов на використання API GitHub, який можна легко використовувати без ключів API. Першою відповіддю на початкове запитання буде:

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

7
@thrau +1 jq він доступний у сховищі і дуже простий у використанні, тому він набагато краще, ніж jsawk. Я випробовував обидва протягом декількох хвилин, jq виграв цю битву
Szymon Sadło

1
Зауважте, що в Python 2, якщо ви переносите вихід на іншу команду, тоді printоператор завжди кодує ASCII, оскільки ви використовуєте Python у трубі. Вставте PYTHONIOENCODING=<desired codec>в команду, щоб встановити інше вихідне кодування, придатне для вашого терміналу. У Python 3 у цьому випадку за замовчуванням є UTF-8 (використовуючиprint() функцію ).
Martijn Pieters

1
Встановіть jq на OSX за допомогою brew install jq
Енді Фралі

1
curl -sеквівалентний curl --silent, тоді як jq -rозначає, jq --raw-outputтобто без рядкових лапок.
Серж Стротобандт

python -c "імпорт запитів; r = questions.get (' api.github.com/users/lambda '); print r.json () [' ім'я '];" . Найпростіший!
NotTooTechy

276

Для швидкого вилучення значень для певного ключа, я особисто люблю використовувати "grep -o", який лише повертає збіг регулярних виразів. Наприклад, щоб отримати поле "текст" з твітів, щось таке:

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

Цей регулярний вираз є більш надійним, ніж можна подумати; наприклад, він чудово справляється з рядками, вбудованими комами та уникнутими цитатами. Я думаю, що за допомогою трохи більше роботи ви зможете зробити той, який фактично гарантовано витягує значення, якщо це атомне. (Якщо в ньому вкладено гніздо, то регулярний вираз не може це зробити.)

І подальші чистий (хоча і зберігаючи початкове витікання струни) , ви можете використовувати що - щось на кшталт: | perl -pe 's/"text"://; s/^"//; s/",$//'. (Я зробив це для цього аналізу .)

Для всіх ненависників, які наполягають, слід використовувати справжній аналізатор JSON - так, це важливо для коректності, але

  1. Зробити дійсно швидкий аналіз, як-от підрахунок значень для перевірки помилок очищення даних або загального відчуття даних, швидше вибиваючи щось із командного рядка. Відкриття редактора для написання сценарію відволікає.
  2. grep -o- на порядок швидше, ніж стандартна jsonбібліотека Python , принаймні, коли це робиться для твітів (що становить ~ 2 КБ кожен). Я не впевнений, чи це просто тому json, що повільно (я повинен колись порівнюватись з yajl); але в принципі, регулярний вираз повинен бути швидшим, оскільки він є кінцевим станом і набагато оптимізованішим, замість аналізатора, який повинен підтримувати рекурсію, і в цьому випадку витрачає багато процесорів на створення дерев для структур, які вам не цікаві. (Якщо хтось написав перетворювач кінцевого стану, який зробив правильний (обмежений глибиною) JSON розбір, це було б фантастично! Тим часом у нас є "grep -o".)

Для написання коду, що підтримується, я завжди використовую справжню бібліотеку розбору. Я не пробував jsawk , але якщо він працює добре, це би вирішило пункт №1.

Останнє, більш веселе, рішення: я написав сценарій, який використовує Python json і витягує потрібні ключі, у стовпчики, розділені на вкладки; то я пропускаю через обгортку навколо, awkщо дозволяє називати доступ до стовпців. Тут: сценарії json2tsv та tsvawk . Отже для цього прикладу було б:

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

Цей підхід не стосується №2, є більш неефективним, ніж один сценарій Python, і він трохи крихкий: він змушує нормалізувати нові рядки та вкладки в рядкових значеннях, щоб грати добре з поглядом на світ / обмеженим записом світу. Але це дозволяє вам залишатися в командному рядку, з більшою коректністю, ніж grep -o.


11
Ви забули про цілі значення. grep -Po '"text":(\d*?,|.*?[^\\]",)'
Роберт

3
Роберт: Правильно, мій регулярний вираз був написаний тільки для рядкових значень для цього поля. Цілі особи можна додати, як ви кажете. Якщо ви хочете всіх типів, вам доведеться робити все більше і більше: booleans, null. А масиви та об’єкти потребують більшої роботи; під типовими регулярними виразами можлива лише обмежена глибина.
Брендан ОКоннор

9
1. jq .nameпрацює в командному рядку і не вимагає "відкриття редактора для написання сценарію". 2. Не має значення, наскільки швидко ваш регулярний вираз може дати неправильні результати
jfs

6
і якщо ви хочете лише значення, ви можете просто кинути на нього awk. | grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
JeffCharter

34
Здається, що в OSX -Pваріант відсутній. Я тестував на OSX 10.11.5 і grep --versionбув grep (BSD grep) 2.5.1-FreeBSD. Я отримав це, працюючи з опцією "розширений регекс" на OSX. Команда зверху була б grep -Eo '"text":.*?[^\\]",' tweets.json.
Єнс

174

Виходячи з того, що деякі рекомендації (тут у коментарях) пропонують використовувати Python, я розчарувався, не знайшовши приклад.

Отже, ось один вкладиш, щоб отримати одне значення з деяких даних JSON. Це передбачає, що ви переносите дані в (звідкись), і тому вони повинні бути корисними в сценарії сценаріїв.

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

Я розширив цю відповідь нижче, щоб використовувати функцію bash: curl 'some_api' | getJsonVal 'key'
Джо Хеймінг

pythonpy( github.com/russell91/pythonpy майже завжди є кращою альтернативою python -c, хоча його потрібно встановлювати за допомогою pip. Просто перекачайте json py --ji -x 'x[0]["hostname"]'. Якщо ви не хочете використовувати вбудовану підтримку json_input, ви все одно можете отримати ті імпортуються автоматично якpy 'json.loads(sys.stdin)[0]["hostname"]'
RussellStewart

2
Дякую! Для більш швидкого та брудного розбору JSON я перетворив його на функцію bash: jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); print($1)"; }щоб я міг написати: curl ...... | jsonq 'json.dumps([key["token"] for key in obj], indent=2)'& більше подібних страшних речей ... Btw, obj[0]здається непотрібним, це виглядає як просто objпрацює нормально у випадках за замовчуванням (?).
akavel

Дякую. Я зробив цю повагу JSON трохи кращим, ніж друк:jsonq() { python -c "import sys,json; obj=json.load(sys.stdin); sys.stdout.write(json.dumps($1))"; }
Адам К Дін

4
obj[0]викликає помилку під час розбору { "port":5555 }. Відмінно працює після видалення [0].
CyberEd

134

Слідом за керівництвом MartinR та Boecko:

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

Це дасть вам надзвичайно приємний вихід. Дуже зручно:

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

37
Як би ви витягли конкретний ключ, як запитує ОП?
червень

2
Найкраща відповідь поки що, не потрібно встановлювати нічого іншого на більшість дистрибутивів, і ви можете | grep field. Дякую!
Андреа Річіарді

7
Все це робить формат JSON, якщо я не помиляюся. Це не дозволяє абоненту вибрати певне поле з виводу, як це було б рішення xpath або щось на основі "JSON Pointer".
Чеезо,

4
Я просто закінчую пару ключових значень, але не значення саме по собі.
Крістофер

1
jqзазвичай не встановлюється, поки python є. Крім того, як тільки ви перейдете на Python, ви також можете пройти цілий шлях і проаналізувати йогоimport json...
CpILL

125

Ви можете просто завантажити jqбінарний файл для своєї платформи та запустити ( chmod +x jq):

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

Він витягує "name"атрибут з об'єкта json.

jqдомашня сторінка говорить, що це як sedдля даних JSON.


27
Тільки для запису, jqце дивовижний інструмент.
хосс

2
Домовились. Я не можу порівняти з jsawk з прийнятої відповіді, так як я цього не використовував, але для локальних експериментів (де встановлення інструменту прийнятно) настійно рекомендую jq. Ось трохи більш обширний приклад, який бере кожен елемент масиву та синтезує новий об’єкт JSON з вибраними даними: curl -s https://api.example.com/jobs | jq '.jobs[] | {id, o: .owner.username, dateCreated, s: .status.state}'
jbyler

2
Люблю це. Дуже невелика вага, і оскільки він знаходиться в звичайному старому C, його можна скласти майже де завгодно.
Benmj

1
Найпрактичніший варіант: йому не потрібні бібліотеки сторонніх виробників (в той час як jsawk), а їх легко встановити (OSX: варити встановити jq)
lauhub

1
Це найбільш практична і легко реалізована відповідь для мого використання. Для системи Ubuntu (14.04) проста програма apt-get install jq додала інструмент до моєї системи. Я перекладаю JSON вихід з відповідей AWS CLI в jq, і він чудово працює для вилучення значень певних клавіш, вкладених у відповідь.
Брендон К

105

Використання Node.js

Якщо система має встановлено, можна використовувати прапори сценарію -pдруку та -eевакуації, JSON.parseщоб витягнути будь-яке необхідне значення.

Простий приклад, використовуючи рядок JSON { "foo": "bar" }і витягуючи значення "foo":

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

Оскільки у нас є доступ до catінших утиліт, ми можемо використовувати це для файлів:

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

Або будь-який інший формат, такий як URL, що містить JSON:

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

1
Дякую! але в моєму випадку він працює лише з прапором -enode -p -e 'JSON.parse(process.argv[1]).foo' '{ "foo": "bar" }'
Rnd_d

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

4
це моє улюблене рішення; використовуйте мову (javascript) для розбору природної для нього структури даних (JSON). видається найбільш правильним . також - вузол, ймовірно, вже доступний у системі, і вам не доведеться обробляти бінарні файли jq (це схоже на інший правильний вибір).
Еліран Малька

Це функція сценарію bash: # jsonv отримає значення об'єкта json для конкретного атрибута # перший параметр - документ json # другий параметр - атрибут, значення якого слід повернути get_json_attribute_value () {node -pe 'JSON.parse (процес. argv [1]) [process.argv [2]] '"$ 1" "$ 2"}
Юнес

6
Наступні роботи з Node.js 10:cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
Ілля Бояндін

100

Використовуйте підтримку JSON Python замість того, щоб використовувати awk!

Щось на зразок цього:

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

6
Вибачте за те, що я намагався знайти хорошу відповідь ...: Я постараюся більше. Для партизанства потрібно більше, ніж писати сценарій awk, щоб позбавити його!
мартін

9
Чому ви використовуєте змінну obj у цьому рішеннях oneliner ?. Це марно і взагалі не зберігається? Ви пишете менш , використовуючи в json.load(sys.stdin)['"key']"якості прикладу , як: curl -sL httpbin.org/ip | python -c "import json,sys; print json.load(sys.stdin)['origin']".
м3нда

64

Ви запитали, як стріляти себе в ногу, і я тут, щоб надати боєприпаси:

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

Ви можете використовувати tr -d '{}' замість цього sed. Але видача їх повністю, здається, також має бажаний ефект.

Якщо ви хочете зняти зовнішні лапки, переведіть результат вищезазначеного наскрізь sed 's/\(^"\|"$\)//g'

Я думаю, що інші забили достатню тривогу. Я буду стояти поруч з мобільним телефоном, щоб викликати швидку допомогу. Пожежте, коли будете готові.


10
Таким чином лежить божевілля, читайте це: stackoverflow.com/questions/1732348/…
Призупинено до подальшого повідомлення.

3
Я прочитав усі відповіді, і ця прекрасно працює для мене без зайвих залежностей. +1
eth0

Ось що я шукав. Єдина корекція - за умови, що команда sed для видалення лапок не працювала для мене, я використав замість sed 's / "// g'
AlexG,

44

Використання Bash з Python

Створіть функцію bash у файлі .bash_rc

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

Тоді

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

Ось така ж функція, але з перевіркою помилок.

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

Якщо $ # -ne 1 гарантує щонайменше 1 вхід, а -t 0 переконайтеся, що ви переспрямовуєтесь з труби.

Приємна річ у цій реалізації полягає в тому, що ви можете отримати доступ до вкладених значень json та отримати json взамін! =)

Приклад:

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

Якщо ви хочете бути по-справжньому фантазійними, ви можете досить роздрукувати дані:

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

Один лайнер без функції bash:curl http://foo | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["environment"][0]["name"]'
Cheeso

1
sys.stdout.write()якщо ви хочете, щоб він працював і з пітоном 2, і з 3.
Пер Йоханссон,

Я думаю, що це має змінитись на system.stdout.write (obj $ 1). Таким чином ви можете сказати: getJsonVal "['environment'] ['name']", як приклад
@Cheeso

1
@Narek У такому випадку це виглядатиме так: функціяgetJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
Joe Heyming

30

TickTick - синтаксичний аналізатор JSON, написаний bash (<250 рядків коду)

Ось авторський фрагмент з його статті " Уявіть собі світ, де Баш підтримує 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
Щодо єдиної надійної чистопорочної відповіді на це, то це заслуговує на більшу кількість результатів.
Ед Рендалл

Чи є можливість знову надрукувати цю змінну людей у ​​рядок json? Це було б надзвичайно корисно
Томас

Нарешті відповідь, що не рекомендують Python чи інші жорстокі методи ... Дякую!
Акіто

21

Розбір JSON з PHP CLI

Можливо, поза темою, але оскільки панує пріоритет, це питання залишається незавершеним без згадки про нашого надійного та вірного PHP, я прав?

Використовуючи той же приклад JSON, але дозволяє призначити його змінній, щоб зменшити невизначеність.

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

Тепер на користь PHP, використовуючи file_get_contents та обгортку потоку php: // stdin .

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

або як вказувалося за допомогою fgets та вже відкритого потоку при CLI константі STDIN .

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

nJoy!


Можна навіть використовувати $argnзамістьfgets(STDIN)
IcanDivideBy0

На жаль, $argnпрацює з прапором -E або -R, і лише якщо вміст JSON знаходиться в одному рядку ...
IcanDivideBy0

21

Версія Native Bash: Також добре працює з косою рисою (\) та котируваннями (")

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

Це круто. Але якщо рядок JSON містить більше одного ключа електронної пошти, аналізатор виведе john@doe.com ""
john@doe.com

Не працює, якщо в електронному листі є тире, як jean-pierre@email.com
alexmngn

13

Версія, яка використовує Ruby та http://flori.github.com/json/

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

або більш стисло:

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

3
це мій улюблений;) До речі, ви можете скоротити його рубіном -rjson, щоб вимагати бібліотеки
lucapette

Зауважте, що фінал ;не потрібно в Ruby (він використовується тільки для об'єднання тверджень, які зазвичай знаходяться в окремих рядках в один рядок).
Зак Морріс

11

На жаль, топ проголосували відповідь , який використовує grepповертає повний матч , який не працював в моєму сценарії, але якщо ви знаєте формат JSON буде залишатися незмінною ви можете використовувати назад ' і попередній для вилучення тільки необхідних значень.

# 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

Ви ніколи не знаєте порядок елементів у словнику JSON. Вони, за визначенням, не упорядковані. Це якраз одна з основних причин того, що прокат власного аналізатора JSON - це приречений підхід.
трійка

10

Якщо хтось просто хоче витягти значення з простих об'єктів JSON без необхідності вкладених структур, можна використовувати регулярні вирази, навіть не залишаючи bash.

Ось функція, яку я визначив, використовуючи bash регулярні вирази на основі стандарту 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
}

Застереження: об'єкти та масиви не підтримуються як значення, але підтримуються всі інші типи значень, визначені у стандарті. Крім того, пара буде відповідна незалежно від того, наскільки глибоко в документі JSON вона знаходиться до тих пір, поки вона має точно таку ж ключову назву.

На прикладі ОП:

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

$ json_extract friends_count "$(curl 'http://twitter.com/users/username.json')"
245

Чи можемо Хелдер Перейра витягти вкладені значення властивостей за допомогою цієї функції?
vsbehere

8

Існує простіший спосіб отримати властивість з рядка json. Використовуючи package.jsonфайл як приклад, спробуйте це:

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

Ми використовуємо, process.envоскільки це передає вміст файлу в node.js як рядок без будь-якого ризику, щоб шкідливий вміст уникнув їх цитування та був розбір як код.


Використання рядкового конкатенації для підстановки значень у рядку, проаналізованому як код, дозволяє запускати довільний код node.js, тобто, надзвичайно небезпечно використовувати випадковий вміст, який ви вийшли з Інтернету. Існує причина, чому безпечні / найкращі методи розбору JSON в JavaScript не просто оцінюють його.
Чарльз Даффі

@CharlesDuffy не впевнений, що я слідую, але виклик JSON.parse повинен бути безпечнішим, як і require()справді запускати іноземний код, JSON.parse не може.
Олександр Міллс

Це правда, якщо-і-тільки, якщо ваша струна насправді вводиться в час виконання JSON таким чином, щоб обійти аналізатор. Я не бачу код тут робити це надійно. Витягніть його зі змінної середовища та передайте його JSON.parse()та так, ви однозначно безпечні ... але тут, час виконання JSON отримує (ненадійний) вміст у діапазоні з (довіреним) кодом.
Чарльз Даффі

... аналогічно, якщо у вас код читає JSON з файлу як рядок і передає цю рядок JSON.parse(), то ви також безпечні, але це теж не відбувається.
Чарльз Даффі

1
... ага, чорт, може також негайно перейти в "як". Проблема полягає в тому, що ви замінюєте змінну оболонки, яку ви збираєтесь передати JSON.parse(), в код . Ви припускаєте, що введення літеральних задників збереже вміст у буквальному сенсі, але це абсолютно небезпечне припущення, тому що буквальні звороти можуть існувати у вмісті файлу (і, таким чином, змінної), і, таким чином, можна припинити цитування і ввести контекст без котирування Значення виконуються як код.
Чарльз Даффі

7

Тепер, коли Powershell є крос-платформою, я подумав, що перекину його там, оскільки вважаю це досить інтуїтивно зрозумілим та надзвичайно простим.

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

ConvertFrom-Json перетворює JSON в спеціальний об'єкт Powershell, тому ви можете легко працювати з властивостями з цього моменту вперед. Якщо ви хотіли, наприклад, властивості "id", ви просто зробите це:

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

Якщо ви хочете покликатись на все з Bash, вам доведеться назвати це так:

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

Звичайно, існує чистий спосіб Powershell зробити це без завитку, який би був:

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

Нарешті, є також "ConvertTo-Json", який так само легко перетворює спеціальний об'єкт у JSON. Ось приклад:

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

Що дозволило б отримати такий хороший JSON:

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

}

Правда, використання оболонки Windows на Unix є дещо неправдоподібним, але Powershell дуже хороший у деяких речах, і розбір JSON та XML - це пара з них. Ця сторінка GitHub для версії для кросплатформ https://github.com/PowerShell/PowerShell


вигідне, оскільки ви просуваєте нову стратегію Microsoft щодо відкриття джерела своїх інструментів та включення іноземних інструментів з відкритим кодом. Це гарна річ для нашого світу.
Алекс

Мені не подобалося PowerShell, але я повинен визнати, що поводження з JSON як об'єктами є досить приємним.
MartinThé

6

Хтось, у якого також є XML-файли, може захотіти подивитися на мій Xidel . Це кліп , незалежний від JSONiq процесор . (тобто він також підтримує XQuery для обробки xml або json)

Приклад у питанні:

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

Або із власним нестандартним синтаксисом розширення:

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

1
Або простіше нині: xidel -s https://api.github.com/users/lambda -e 'name'(або -e '$json/name', або -e '($json).name').
Рейно

6

Я не можу використати тут жодної відповіді. Немає доступних jq, немає масивів оболонок, немає оголошення, немає grep -P, немає перспектив і lookahead, ні Python, ні Perl, ні Ruby, ні - навіть Bash ... Залишилися відповіді просто не працюють добре. JavaScript прозвучав знайомо, але олово каже Nescaffe - так що це теж не йде :) Навіть якщо вони будуть доступні, для моєї простої потреби - вони будуть надмірними і повільними.

Тим не менше, для мене надзвичайно важливо отримати багато змінних з форматованої відповіді json мого модему. Я роблю це в ш, з дуже підстриженим BusyBox на моїх маршрутизаторах! Немає проблем із використанням однієї програми awk: просто встановіть роздільники та прочитайте дані. Для однієї змінної це все!

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

Пам'ятаєте, у мене немає масивів? Мені довелося призначити в межах awk проаналізовані дані 11 змінним, які мені потрібні в сценарії оболонки. Куди б я не глянув, це говорилося про неможливу місію. З цим теж немає проблем.

Моє рішення просте. Цей код: 1) розбере .json-файл із запитання (фактично я запозичив зразок робочих даних із найбільш актуалізованої відповіді) та виберу цитовані дані, плюс 2) створить змінні оболонки зсередини awk, призначивши вільну названу оболонку назви змінних.

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"

Немає проблем із заготовками всередині. У моєму використанні та сама команда аналізує довгий вихід одного рядка. Оскільки використовується eval, це рішення підходить лише для надійних даних. Його легко адаптувати до підбору даних, котируються без котирування. Для величезної кількості змінних, граничний приріст швидкості можна досягти, використовуючи ще, якщо. Відсутність масиву, очевидно, означає: відсутність декількох записів без зайвих фейдів. Але там, де доступні масиви, адаптувати це рішення - це просте завдання.

@maikel sed відповідь майже працює (але я не можу коментувати це). Для моїх добре відформатованих даних - це працює. Не так багато з прикладу, який тут використовується (відсутні цитати викидають його). Це складно і важко модифікувати. Крім того, мені не подобається робити 11 дзвінків, щоб витягти 11 змінних. Чому? Я приуротив 100 циклів, витягуючи 9 змінних: функція sed займала 48,99 сек, а мій розчин займав 0,91 сек! Нечесно? Виконується лише один видобуток 9 змінних: 0,51 проти 0,02 сек.


5

Ви можете спробувати щось подібне -

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

5

Ви можете використовувати jshon:

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

На сайті написано: "Вдвічі швидше, 1/6 пам'яті" ... а потім: "Jshon розбирає, читає і створює JSON. Він розроблений таким чином, щоб бути максимально зручним зсередини оболонки і замінює крихкі аналізатори пар, зроблені з grep / sed / awk, а також важкі однолінійні парсери з perl / python. "
Роджер

це вказано як рекомендоване рішення для розбору JSON у Bash
qodeninja

який найпростіший спосіб позбутися цитат навколо результату?
gMale

4

ось один із способів зробити це з 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

Для більш складного розбору JSON я пропоную використовувати модуль python jsonpath (від Стефана Госснера) -

  1. Встановити його -

sudo easy_install -U jsonpath

  1. Використай це -

Приклад file.json (від 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
    }
  }
}

Розберіть його (витягніть усі назви книг із ціною <10) -

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

Вийде -

Sayings of the Century
Moby Dick

ПРИМІТКА. Вищевказаний командний рядок не включає перевірку помилок. для повного рішення з перевіркою помилок ви повинні створити невеликий скрипт python та обернути його кодом за допомогою випробування.


прекрасна ідіома. Я навіть не знаю Python, але це здається потужним рішенням
Шрідхар Сарнобат

У мене виникли невеликі проблеми з встановленням jsonpathвстановленого jsonpath_rwнатомість, тому ось щось подібне ви можете спробувати, якщо вищезгадане не працює: 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;"(Я використав повний шлях до бінарного пітона, тому що у мене виникли проблеми з кількома пітонами встановлено).
Шрідхар Сарнобат

4

Якщо у вас є php :

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

Наприклад: у
нас є ресурс, який надає json країнам iso-коди: http://country.io/iso3.json, і ми можемо легко бачити його в оболонці з curl:

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

але це виглядає не дуже зручно і не читається, краще проаналізуйте json і побачити зручну структуру:

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

Цей код надрукує щось на зразок:

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

якщо ви вклали масиви, цей вихід буде виглядати набагато краще ...

Сподіваюся, що це допоможе ...


4

Також є дуже простий, але потужний інструмент для обробки JSON CLI fx - https://github.com/antonmedv/fx

Приклад форматування JSON в терміналі Bash

Приклади

Використовуйте анонімну функцію:

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

Якщо ви не передасте анонімну функцію param => ..., код буде автоматично перетворений в анонімну функцію. І ви можете отримати доступ до JSON за цим ключовим словом:

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

Або просто також використовуйте синтаксис крапки:

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

Ви можете передавати будь-яку кількість анонімних функцій для зменшення JSON:

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

Ви можете оновити існуючий JSON за допомогою оператора спред:

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

Просто звичайний JavaScript . Не потрібно вивчати новий синтаксис.


ОНОВЛЕННЯ 2018-11-06

fxтепер має інтерактивний режим ( ! )

https://github.com/antonmedv/fx


7
Якщо ви рекламуєте своє власне творіння, вам потрібно чітко розповісти про це. Дивіться, як не бути спамером.
трійка

4

Це ще одна bashі pythonгібридна відповідь. Я опублікував цю відповідь, тому що хотів обробити більш складний вихід JSON, але, зменшивши складність моєї програми bash. Я хочу зламати наступний об’єкт JSON з http://www.arcgis.com/sharing/rest/info?f=json у bash:

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

У наступному прикладі я створив власну реалізацію jqта unquoteвикористання python. Ви помітите, що щойно ми імпортуємо об’єкт jsonpython з словника python, ми можемо використовувати синтаксис python для навігації по словнику. Для навігації до вищезазначеного синтаксис:

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

Використовуючи магію в баші, ми опускаємо dataі лише подаємо текст пітона праворуч від даних, тобто

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

Зверніть увагу, без параметрів, jq виступає як затискач JSON. За допомогою параметрів ми можемо використовувати синтаксис python для вилучення всього словника зі словника, включаючи навігацію до підручників та елементів масиву.

Ось робочий приклад, який демонструє вищесказане:

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

Я зробив це, "проаналізувавши" відповідь json для певного значення таким чином:

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

Зрозуміло, що $ url тут буде URL-адресою Twitter, а $ var - "текстом", щоб отримати відповідь на цей var.

Дійсно, я думаю, що єдине, що я робив, що ОП покинув, - це греп за лінію з конкретною змінною, яку він прагне. Awk хапає другий пункт на рядку, і sed забираю лапки.

Хтось розумніший за мене, мабуть, міг би роздумати цілком роздуто чи жартівливо.

Тепер ви можете це зробити лише за допомогою sed:

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

таким чином, ні гад, ні греп ... Я не знаю, чому я не думав про це раніше. Гммм ...


Власне, з sed можна зробити
tonybaldwin

1
grep | awk | sedІ sed | sed | sedтрубопроводи марнотратних antipatterns. Ваш останній приклад можна легко переписати, curl "$url" | sed '/text/!d;s/\"text\"://g;s/\"//g;s/\ //g'але, як зазначають інші, це такий і схильний до помилок і крихкий підхід, який не слід рекомендувати в першу чергу.
трійка

Мені довелося використовувати grep -oPz 'ім'я \ ": \". *? \ "' Curloutput | sed 's / name \": / \ n / g'
Ferroao

3

Розбір JSON болючий у сценарії оболонки. З більш підходящою мовою створіть інструмент, який витягує атрибути JSON таким чином, що відповідає конвенціям про сценарій оболонки. Ви можете використовувати свій новий інструмент для вирішення негайної проблеми з сценарієм оболонки, а потім додати його до свого набору для майбутніх ситуацій.

Наприклад, розгляньте інструмент jsonlookup таким, що якщо я скажу, jsonlookup access token idвін поверне ідентифікатор атрибута, визначений в токені атрибута, визначеному в доступі до атрибуту від stdin, який, імовірно, є даними JSON. Якщо атрибута не існує, інструмент нічого не повертає (статус виходу 1). Якщо синтаксичний розбір не вдається, вийдіть із статусом 2 та наведіть повідомлення для stderr. Якщо пошук успішний, інструмент друкує значення атрибута.

Створивши інструмент unix для точної мети вилучення значень JSON, ви можете легко використовувати його в скриптах оболонки:

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

Будь-яка мова буде робити для реалізації jsonlookup . Ось досить лаконічна версія пітона:

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

Дволінійний, який використовує пітон. Це особливо добре працює, якщо ви пишете один .sh-файл і не хочете залежати від іншого .py-файлу. Він також використовує трубу |. echo "{\"field\": \"value\"}"можна замінити будь-яким друком json на stdout.

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

Питання не шукало рішення Python. Дивіться також коментарі.
Ендрю Барбер

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.