- Parsing JSON with Unix tools
- 46 Answers 46
- Frequently Asked Questions
- Why not a pure shell solution?
- Why not use awk, sed, or grep?
- Historical notes
- Работа с JSON в bash с помощью jq
- Почему бы просто не использовать node.js, когда вам нужно работать с JSON?
- Установка jq
- Основы работы с jq
- Получить свойство
- Итерация
- Функции jq
- Создание объектов
- Давайте теперь используем его по-настоящему
- Похожие записи:
Parsing JSON with Unix tools
Erm that is not good json parsing btw. what about the escape characters in strings. etc IS there a python answer to this on SO (a perl answer even. )?
The Python answer to this is to simply use a Python JSON library that will actually parse the JSON. sed and AWK provide regular expressions, but those are not a good solution to the problem of correctly parsing JSON.
Any time someone says «problem X can easily be solved with other language Y,» that’s code for «my toolbox has only a rock for driving nails. why bother with anything else?»
@BryanH: except sometimes language Y can be more equipped to solve particular problem X regardless of how many languages the person who suggested Y knows.
Kinda late, but here it goes. grep -Po ‘»‘»version»‘»\s*:\s*»\K([^»]*)’ package.json . This solves the task easily & only with grep and works perfectly for simple JSONs. For complex JSONs you should use a proper parser.
46 Answers 46
There are a number of tools specifically designed for the purpose of manipulating JSON from the command line, and will be a lot easier and more reliable than doing it with Awk, such as jq :
curl -s 'https://api.github.com/users/lambda' | jq -r '.name'
You can also do this with tools that are likely already installed on your system, like Python using the json module, and so avoid any extra dependencies, while still having the benefit of a proper JSON parser. The following assume you want to use UTF-8, which the original JSON should be encoded in and is what most modern terminals use as well:
curl -s 'https://api.github.com/users/lambda' | \ python3 -c "import sys, json; print(json.load(sys.stdin)['name'])"
export PYTHONIOENCODING=utf8 curl -s 'https://api.github.com/users/lambda' | \ python2 -c "import sys, json; print json.load(sys.stdin)['name']"
Frequently Asked Questions
Why not a pure shell solution?
The standard POSIX/Single Unix Specification shell is a very limited language which doesn’t contain facilities for representing sequences (list or arrays) or associative arrays (also known as hash tables, maps, dicts, or objects in some other languages). This makes representing the result of parsing JSON somewhat tricky in portable shell scripts. There are somewhat hacky ways to do it, but many of them can break if keys or values contain certain special characters.
Bash 4 and later, zsh, and ksh have support for arrays and associative arrays, but these shells are not universally available (macOS stopped updating Bash at Bash 3, due to a change from GPLv2 to GPLv3, while many Linux systems don’t have zsh installed out of the box). It’s possible that you could write a script that would work in either Bash 4 or zsh, one of which is available on most macOS, Linux, and BSD systems these days, but it would be tough to write a shebang line that worked for such a polyglot script.
Finally, writing a full fledged JSON parser in shell would be a significant enough dependency that you might as well just use an existing dependency like jq or Python instead. It’s not going to be a one-liner, or even small five-line snippet, to do a good implementation.
Why not use awk, sed, or grep?
It is possible to use these tools to do some quick extraction from JSON with a known shape and formatted in a known way, such as one key per line. There are several examples of suggestions for this in other answers.
However, these tools are designed for line based or record based formats; they are not designed for recursive parsing of matched delimiters with possible escape characters.
So these quick and dirty solutions using awk/sed/grep are likely to be fragile, and break if some aspect of the input format changes, such as collapsing whitespace, or adding additional levels of nesting to the JSON objects, or an escaped quote within a string. A solution that is robust enough to handle all JSON input without breaking will also be fairly large and complex, and so not too much different than adding another dependency on jq or Python.
I have had to deal with large amounts of customer data being deleted due to poor input parsing in a shell script before, so I never recommend quick and dirty methods that may be fragile in this way. If you’re doing some one-off processing, see the other answers for suggestions, but I still highly recommend just using an existing tested JSON parser.
Historical notes
This answer originally recommended jsawk, which should still work, but is a little more cumbersome to use than jq , and depends on a standalone JavaScript interpreter being installed which is less common than a Python interpreter, so the above answers are probably preferable:
curl -s 'https://api.github.com/users/lambda' | jsawk -a 'return this.name'
This answer also originally used the Twitter API from the question, but that API no longer works, making it hard to copy the examples to test out, and the new Twitter API requires API keys, so I’ve switched to using the GitHub API which can be used easily without API keys. The first answer for the original question would be:
curl 'http://twitter.com/users/username.json' | jq -r '.text'
Работа с JSON в bash с помощью jq
jq — это мощный инструмент, позволяющий читать, фильтровать и писать JSON в bash.
Возможно, вы видели или даже писали bash, который выглядит следующим образом:
curl -s 'https://some-random-api.ml/others/joke' \ | python -m json.tool \ | grep '\"joke\"' \ | cut -d ':' -f 2 \ | sed 's/"/\"/g'
Это трудно читать и еще труднее писать. Чтобы добраться до свойства в теле ответа JSON, вам приходится обращаться к 4 различным утилитам! Bash не понимает JSON из коробки, и использование типичных инструментов для работы с текстом, таких как grep, sed или awk, становится затруднительным. К счастью, есть лучший способ с помощью инструмента под названием jq.
jq может упростить приведенный выше bash до следующего:
curl -s "https://some-random-api.ml/others/joke" | jq '.joke'
Это гораздо приятнее . Упрощая работу с JSON в bash, jq открывает множество возможностей автоматизации, для которых в противном случае мне пришлось бы писать что-то на node.js (что не так уж плохо, просто обычно это занимает больше времени).
Почему бы просто не использовать node.js, когда вам нужно работать с JSON?
Иногда node.js является правильным инструментом. Для большинства задач автоматизации я предпочитаю использовать bash, когда это возможно, потому что он быстрее и даже более портативен (я могу поделиться сценарием bash с членами команды, у которых не установлен node.js). На мой взгляд, bash более выразителен и лаконичен для определенных задач, чем node.
Установка jq
jq не является встроенной командой ни в одну среду, поэтому вам придется установить его. Запустите brew install jq на macOS. О том, как установить jq в других средах, читайте на странице справки по установке.
Основы работы с jq
jq работает аналогично sed или awk — как фильтр, в который вы передаете данные и из которого извлекаете значения. Также как sed или awk, он, по сути, имеет свой собственный специфический язык (DSL) для запросов JSON. К счастью, он очень интуитивно понятен (в отличие от awk)
Получить свойство
Допустим, у нас есть JSON, который выглядит следующим образом:
Чтобы вывести свойство foo, мы используем оператор ., за которым следует имя свойства.
Это выведет 123, как и ожидалось.
Это работает и с вложенностью. Так .a.b.c.d будет перемещаться вниз по свойствам вложенных объектов.
Это само по себе довольно полезно. Для реалистичного и абсолютно полезного примера давайте напишем сценарий, который получает астрономическую картинку дня и устанавливает ее в качестве обоев (это только для macOS).
# получите URL-адрес текущей астрономической картинки дня (APOD) apod_url=$(curl -s https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY | jq -r '.hdurl') # получить только имя изображения из URL filepath=$(basename "$apod_url") # Теперь получите изображение и сохраните его curl -s -o "$filepath" "$apod_url" # Используйте AppleScript, чтобы установить его в качестве обоев osascript -e "tell application \"Finder\" to set desktop picture to POSIX file \"$PWD/$filepath\""
Обратите внимание, что если в свойстве есть пробелы или странные символы, вам придется использовать кавычки. Например:
echo '< "Version Number": "1.2.3" >' | jq '. "Version Number"'
Кроме того, убедитесь, что вы всегда оборачиваете селектор jq в одинарные кавычки, иначе bash попытается интерпретировать все символы как ., тогда как мы хотим, чтобы это делал jq.
Итерация
Теперь давайте посмотрим, как работает итерация. Оператор итератора массива или значения объекта, .[], делает это возможным.
Это выведет 1, 2, 3 на отдельных строках.
В массиве объектов вы можете получить доступ к свойству каждого элемента массива следующим образом:
Или для объекта, .[] выведет значение каждой пары ключ/значение:
Обратите внимание, что вы также можете передать индекс в .[], поэтому
Теперь как нам сделать что-то для каждой строки? Точно так же, как в bash вы обрабатываете все, что выводит несколько строк информации: xargs, циклы for, или некоторые команды, просто обрабатывающие несколько элементов ввода, и т.д. Например:
Функции jq
В jq также есть встроенные «функции». Возвращаясь к предыдущему примеру итерации объекта — допустим, мы хотим получить ключи объекта (не значения) в виде массива:
что вернет a b . Обратите внимание, что мы также используем оператор pipe |, который работает в jq так же, как и в bash — он принимает вывод слева и передает его в качестве ввода справа.
Еще одна удобная функция для массивов и объектов — это функция length, которая возвращает свойство длины массива или количество свойств объекта.
Можно поступить еще более причудливо и создавать промежуточные массивы и объекты на лету. Здесь я объединяю ключи объектов dependencies и devDependencies (из файла package.json) в массив, сглаживаю его, а затем получаю длину.
jq -r '[(.dependencies, .devDependencies) | keys] | flatten | length' package.json
Это возвращает количество зависимостей и devDependencies, которые содержит package.json.
Создание объектов
Вы также можете создавать объекты «на лету». Это может быть полезно для изменения формы некоторых JSON. Например:
Давайте теперь используем его по-настоящему
Что если я захочу провести аудит зависимостей в package.json и удалить все, что не используется? Неиспользуемые зависимости замедляют установку npm для всех и просто мешают. Я мог бы вручную проверить использование каждой зависимости (через grep или в IDE), но если у вас много зависимостей, это быстро надоедает, поэтому давайте автоматизируем это.
set -e # функция для поиска зависимостей grep_dep() < # параметры: $1 = строка для поиска, $2 = каталог для поиска в # [1] grep --include="*.js" --exclude-dir="node_modules" -R --color -n "require\(.*$1.*\)" "$2" # если grep возвращает 0 результатов, то код выхода равен 1. Отсутствие результатов означает, что зависимость не используется if [[ $? >0 ]]; then echo echo "** Использование $1 не найдено, рассмотрите возможность удаления. **" echo fi > # [2] export -f grep_dep # [3] jq -r '.dependencies | keys | .[]' package.json | \ # [4] xargs -I '<>' -P 4 -t bash -c "grep_dep '<>' ."
[1] Вот как работают флаги grep:
- -include и -exclude-dir сужают круг файлов, которые будут искаться.
- -R означает рекурсивный, указывает на поиск всех совпадающих файлов
- -color окрашивает вывод
- -n отображает номера строк
[2] Я должен экспортировать его, чтобы подпрограмма могла его увидеть. Если вы хотите, чтобы xargs вызывал пользовательскую функцию, вам придется вызывать ее в подпрограмме по некоторым причинам.
[3] -r означает «raw-output», то есть без кавычек вокруг значений, что делает его пригодным для обработки в других командах bash. Мы получаем имена зависимостей в виде массива (это эквивалентно Object.keys(require(‘./package.json’).dependencies) в node.js)
[4] Затем мы передаем их в xargs, который обрабатывает настройку grep для каждой строки. Вот как работают все флаги xargs:
- -t указывает, что нужно передать эхом построенную команду; полезно для отладки
- -I <> определяет строку замены, куда будет помещена строка зависимости
- -P 4 определяет параллельность, так что будет выполняться 4 одновременные команды greps
мы говорим ему запустить подпрограмму bash, в которой будет вызвана наша функция grep_dep с ее аргументами.
В этом скрипте можно сделать еще много чего интересного, чтобы сделать его более надежным, но это основная суть.
jq — это потрясающий инструмент, который упрощает работу с JSON в bash. DSL для фильтрации, запросов и создания JSON намного глубже, чем то, что я описал здесь, так что смотрите https://stedolan.github.io/jq/manual/.