Разбор JSON с инструментами Unix

880

Я пытаюсь проанализировать JSON, возвращенный из запроса curl, вот так:

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, кстати ... как насчет escape-символов в строках ... и т.д. Есть ли на SO ответ на python (даже на perl ...)?
марта
51
Каждый раз, когда кто-то говорит: «Проблема X может быть легко решена с помощью другого языка Y», это код для «моей панели инструментов есть только камень для гвоздей ... зачем беспокоиться о чем-то еще?»
BryanH
22
@BryanH: за исключением того, что иногда язык Y может быть более приспособлен для решения конкретной проблемы X независимо от того, сколько языков знает человек, предложивший Y.
JFS
15
Вроде поздно, но здесь это идет. grep -Po '"'"version"'"\s*:\s*"\K([^"]*)' package.json, Это решает задачу легко и только с помощью grep и отлично работает для простых JSON. Для сложных JSON вы должны использовать правильный парсер.
diosney
2
@auser, вы могли бы согласиться с тем, чтобы в названии редактировалось слово «с помощью sed и awk» на «с инструментами UNIX»?
Чарльз Даффи

Ответы:

1128

Существует ряд инструментов, специально разработанных для манипулирования JSON из командной строки, и будет намного проще и надежнее, чем с Awk, например jq:

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

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

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

Исторические заметки

Первоначально этот ответ рекомендовал jsawk , который все еще должен работать, но его использование несколько сложнее jqи зависит от устанавливаемого автономного интерпретатора JavaScript, который менее распространен, чем интерпретатор Python, поэтому приведенные выше ответы, вероятно, предпочтительнее:

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

В этом ответе также изначально использовался API-интерфейс Twitter из этого вопроса, но этот API-интерфейс больше не работает, что затрудняет копирование примеров для тестирования, а новому API-интерфейсу Twitter требуются ключи API, поэтому я перешел к использованию GitHub API, который можно легко использовать без ключей API. Первый ответ на оригинальный вопрос будет:

curl 'http://twitter.com/users/username.json' | jq -r '.text'
Брайан Кэмпбелл
источник
7
@thrau +1. jq он доступен в репозитории и очень прост в использовании, поэтому он намного лучше, чем jsawk. Я проверил оба в течение нескольких минут, jq выиграл эту битву
Шимон Садло
1
Обратите внимание, что в Python 2, если вы передаете вывод в другую команду, printоператор всегда будет кодироваться в ASCII, потому что вы используете Python в конвейере. Вставьте PYTHONIOENCODING=<desired codec>в команду, чтобы установить другую выходную кодировку, подходящую для вашего терминала. В Python 3 значением по умолчанию является UTF-8 в этом случае (с использованием print() функции ).
Мартин Питерс
1
Установите jq на OSX с помощью brew install jq
Энди
1
curl -sэквивалентно curl --silent, тогда как jq -rозначает, jq --raw-outputто есть без строковых кавычек.
Серж Строобандт
python -c "запросы на импорт; r = запросы.get (' api.github.com/users/lambda');print r.json () [' name '];" , Самый простой!
NotTooTechy
277

Чтобы быстро извлечь значения для определенного ключа, мне лично нравится использовать «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, и он немного хрупок: он заставляет нормализовать переводы строк и табуляции в строковых значениях, чтобы хорошо сочетаться с представлением awk в поле / записи с разделением по полю мира. Но это позволяет вам оставаться в командной строке с большей точностью, чем grep -o.

Брендан Оконнор
источник
11
Вы забыли о целочисленных значениях. grep -Po '"text":(\d*?,|.*?[^\\]",)'
Роберт
3
Роберт: Да, мое регулярное выражение было написано только для строковых значений для этого поля. Целые числа могут быть добавлены, как вы говорите. Если вы хотите все типы, вы должны делать больше и больше: логическое значение, ноль. И массивы и объекты требуют больше работы; возможно только ограничение по глубине при стандартных регулярных выражениях.
Брендан Оконнор
9
1. jq .nameработает в командной строке и не требует «открытия редактора для написания скрипта». 2. Неважно, как быстро ваше регулярное выражение может давать неправильные результаты
JFS
6
и если вам нужны только значения, вы можете просто добавить к ним awk. | grep -Po '"text":.*?[^\\]",'|awk -F':' '{print $2}'
Джеффчартер
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"]'
paulkmoore
источник
Я расширил этот ответ ниже, чтобы использовать функцию 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)'и другие подобные страшные вещи ... Кстати, obj[0]кажется ненужным, похоже, что он objработает нормально в случаях по умолчанию (?).
акавел
Спасибо. Я сделал это уважение 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

Это даст вам чрезвычайно дружественный вывод grep. Очень удобно:

$ curl -s 'http://twitter.com/users/username.json' | python -mjson.tool | grep my_key
jnrg
источник
37
Как бы вы извлекли конкретный ключ, как спрашивает OP?
хуан
2
Лучший ответ на данный момент imho, нет необходимости устанавливать что-либо еще в большинстве дистрибутивов, и вы можете | grep field. Спасибо!
Андреа Ричиарди
7
Все это делает формат JSON, если я не ошибаюсь. Он не позволяет вызывающей стороне выбрать конкретное поле из вывода, как решение xpath или что-то на основе «указателя JSON».
Cheeso
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.

JFS
источник
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: brew install jq)
lauhub
1
Это самый практичный и легко реализуемый ответ для моего варианта использования. Для системы Ubuntu (14.04) простая apt-get install jq добавила инструмент в мою систему. Я передаю вывод JSON из ответов CLI AWS в jq, и он прекрасно работает для извлечения значений для определенных ключей, вложенных в ответ.
Брэндон К
105

Использование Node.js

Если система имеет после установки можно использовать флаги сценария -pprint и -eevaulate, 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
JayQuerie.com
источник
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 (process. ARGV [1]) [process.argv [2]]» "$ 1" "$ 2"}
Юнесс
6
С Node.js 10 работает следующее:cat package.json | node -pe 'JSON.parse(fs.readFileSync(0)).version'
Илья Бояндин
100

Используйте поддержку Python JSON вместо awk!

Что-то вроде этого:

curl -s http://twitter.com/users/username.json | \
    python -c "import json,sys;obj=json.load(sys.stdin);print obj['name'];"
martinr
источник
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']".
m3nda
65

Вы спросили, как выстрелить себе в ногу, и я здесь, чтобы предоставить боеприпасы:

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"
}
Джо Хейминг
источник
curl http://foo | python -c 'import json,sys;obj=json.load(sys.stdin);print obj["environment"][0]["name"]'
Однострочник
1
sys.stdout.write()если вы хотите, чтобы он работал с Python 2 и 3.
Per Johansson
Я думаю, что это должно измениться на system.stdout.write (obj $ 1). Таким образом, вы можете сказать: getJsonVal "['environment'] ['name']", как пример
@Cheeso
1
@ Нарек В таком случае это будет выглядеть так: функцияgetJsonVal() { py -x "json.dumps(json.loads(x)$1, sort_keys=True, indent=4)"; }
Джо Хейминг
30

TickTick - это анализатор JSON, написанный на bash (<250 строк кода)

Вот отрывок из его статьи « Представьте мир, в котором Bash поддерживает JSON» :

#!/bin/bash
. ticktick.sh

``  
  people = { 
    "Writers": [
      "Rod Serling",
      "Charles Beaumont",
      "Richard Matheson"
    ],  
    "Cast": {
      "Rod Serling": { "Episodes": 156 },
      "Martin Landau": { "Episodes": 2 },
      "William Shatner": { "Episodes": 2 } 
    }   
  }   
``  

function printDirectors() {
  echo "  The ``people.Directors.length()`` Directors are:"

  for director in ``people.Directors.items()``; do
    printf "    - %s\n" ${!director}
  done
}   

`` people.Directors = [ "John Brahm", "Douglas Heyes" ] ``
printDirectors

newDirector="Lamont Johnson"
`` people.Directors.push($newDirector) ``
printDirectors

echo "Shifted: "``people.Directors.shift()``
printDirectors

echo "Popped: "``people.Directors.pop()``
printDirectors
CoolAJ86
источник
2
В качестве единственного надежного ответа на этот вопрос чисто заслуживает большего.
Эд Рэндалл
Есть ли способ снова напечатать эту переменную people в строку json? Это было бы чрезвычайно полезно
Томас
1
Наконец ответ, не рекомендующий 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!

nickl-
источник
Вы даже можете использовать $argnвместоfgets(STDIN)
IcanDivideBy0
Упс, $argnработает с флагом -E или -R и только если содержимое JSON находится в одной строке ...
IcanDivideBy0
21

Родная версия 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
Maikel
источник
Это круто. Но если строка JSON содержит более одного ключа электронной почты, анализатор выведет john@doe.com "" john@doe.com
rtc11
Не работает, если в письме есть тире, например, 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]);"
boecko
источник
3
это мой любимый;) Кстати, вы можете сократить его с помощью ruby ​​-rjson, чтобы потребовать библиотеку
lucapette
Обратите внимание, что final ;не требуется в 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-парсера является обреченным подходом.
tripleee
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, если она имеет абсолютно одинаковое имя ключа.

Используя пример 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
Хелдер Перейра
источник
Хелдер Перейра, можем ли мы извлечь значения вложенных свойств с помощью этой функции?
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 сделать это без curl, который будет:

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

user2233949
источник
проголосовал за то, что вы продвигаете новую стратегию Microsoft по открытому исходному коду своих инструментов и включаете в себя иностранные инструменты с открытым исходным кодом. Это хорошо для нашего мира.
Алекс
Раньше мне не нравился PowerShell, но я должен признать, что обработка JSON довольно хороша.
MartinThé
6

Кто-то, у кого также есть XML-файлы, может захотеть взглянуть на мой Xidel . Это cli, не зависящий от JSONiq процессор. (т.е. он также поддерживает XQuery для обработки XML или JSON)

Пример в вопросе будет:

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

Или с моим собственным нестандартным синтаксисом расширения:

 xidel -e 'json("http://twitter.com/users/username.json").name'
BeniBela
источник
1
Или проще в наше время: xidel -s https://api.github.com/users/lambda -e 'name'(или -e '$json/name', или -e '($json).name').
Рейно
6

Я не могу использовать ни один из ответов здесь. Нет доступных jq, нет массивов оболочек, нет объявлений, нет grep -P, нет look -hind и lookahead, нет Python, нет Perl, нет Ruby, нет - даже нет Bash ... Остальные ответы просто не работают хорошо. JavaScript звучит знакомо, но в банке написано Nescaffe - так что это тоже не пойдет :) Даже если таковые имеются, для моей простой необходимости - они будут излишними и медленными.

Тем не менее, для меня чрезвычайно важно получить много переменных из ответа моего модема в формате json. Я делаю это в sh с очень урезанным 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, это решение подходит только для доверенных данных. Его легко адаптировать к данным без кавычек. Для огромного числа переменных, предельное увеличение скорости может быть достигнуто с помощью else if. Отсутствие массива, очевидно, означает: нет нескольких записей без лишних действий. Но там, где имеются массивы, адаптация этого решения - простая задача.

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

Pila
источник
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. Он предназначен для максимально возможного использования из оболочки и заменяет хрупкие парсеры adhoc, созданные из grep / sed / awk, а также
Роджер
это указано в качестве рекомендуемого решения для анализа 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
        }
    }
}'
ghostdog74
источник
4

Для более сложного анализа JSON я предлагаю использовать модуль jsonpath python (автор Stefan Goessner) -

  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 и обернуть код в try-exc.

shlomosh
источник
красивая идиома. Я даже не знаю 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;"(Я использовал полный путь к двоичному файлу python, потому что у меня были некоторые проблемы с несколькими питонами установлены).
Шридхар Сарнобат
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
Если вы продвигаете свое собственное творение, вы должны быть откровенны в этом. Смотрите, как не быть спамером.
tripleee
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. Вы заметите, что как только мы импортируем объект python из jsonсловаря python, мы можем использовать синтаксис python для навигации по словарю. Для навигации выше, синтаксис:

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

Используя магию в bash, мы опускаем dataи поставляем только текст на Python справа от данных, т.е.

  • 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-адресом твиттера, а $ var будет «текстом», чтобы получить ответ для этой переменной.

На самом деле, я думаю, что единственное, что я делаю в OP, это grep для строки с конкретной переменной, которую он ищет. Awk берет второй элемент в строке, и с помощью sed я убираю цитаты.

Кто-то умнее меня, вероятно, мог бы все мыслить с помощью awk или grep.

Теперь вы можете сделать все это с помощью sed:

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

таким образом, нет awk, нет grep ... Я не знаю, почему я не думал об этом раньше. Хммм ...

tonybaldwin
источник
На самом деле, с sed вы можете сделать
Tonybaldwin
1
grep | awk | sedИ sed | sed | sedтрубопроводы расточительных antipatterns. Ваш последний пример можно легко переписать, curl "$url" | sed '/text/!d;s/\"text\"://g;s/\"//g;s/\ //g'но, как уже отмечали другие, это подверженный ошибкам и хрупкий подход, который не следует рекомендовать в первую очередь.
tripleee
Мне пришлось использовать grep -oPz 'name \ ": \". *? \ "' Curloutput | sed 's / name \": / \ n / g'
Ferroao
3

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

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

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

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

Любой язык подойдет для реализации jsonlookup . Вот довольно лаконичная версия 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
mcnabicus
источник
3

Двухслойный, который использует Python. Это особенно хорошо работает, если вы пишете один файл .sh и не хотите зависеть от другого файла .py. Это также использует использование трубы |. echo "{\"field\": \"value\"}"может быть заменено на что-либо, печатающее JSON на стандартный вывод.

echo "{\"field\": \"value\"}" | python -c 'import sys, json
print(json.load(sys.stdin)["field"])'
Адам Куркевич
источник
Вопрос не в поиске решения Python. Смотрите комментарии тоже.
Эндрю Барбер