Вопрос или проблема
Рассмотрим надуманный пример, используя JSON-объект, такой как этот, где я хочу извлечь связанные поля id
, firstname
и lastname
для каждого из многих объектов массива в переменные shell для дальнейшей обработки (не-JSON).
{
"customers": [
{
"id": 1234,
"firstname": "John",
"lastname": "Smith",
"other": "fields",
"are": "present",
"here": "etc."
},
{
"id": 2468,
"firstname": "Janet",
"lastname": "Green",
"other": "values",
"are": "probably",
"here": "maybe"
}
]
}
Для простых данных я могу использовать это,
jq -r '.customers[] | (.id + " " + .firstname + " " + .lastname)' <data.json |
while IFS=' ' read id firstname lastname
do
# Дополнительная обработка, но опущена для примера
printf '%s -- %s -- %s\n' "$id" "$firstname" "$lastname"
done
Вывод
1234 -- John -- Smith
2468 -- Janet -- Green
но, конечно, это потерпит неудачу с двойными именами firstname
, такими как Anne Marie
. Изменение разделителя на другой символ, такой как #
, кажется скорее уловкой, чем решением, но может быть приемлемым.
Для более сложных ситуаций я могу выбрать список значений id
, а затем обменять скорость на точность, вернувшись, чтобы извлечь соответствующие элементы firstname
и lastname
. Что-то вроде этого:
jq -r '.customers[].id' <data.json |
while IFS= read id
do
block=$(jq -r --arg id "$id" '.customers[] | select(.id == $id)' <data.json);
firstname=$(jq -r '.firstname' <<<"$block")
lastname=$(jq -r '.lastname' <<<"$block")
# Дополнительная обработка, но опущена для примера
printf '%s -- %s -- %s\n' "$id" "$firstname" "$lastname"
done
Вывод
1234 -- John -- Smith
2468 -- Janet -- Green
Однако ни один из этих методов не является одновременно правильным и эффективным. Хотя я не собираюсь запускать настоящий код с высокой частотой, мне бы хотелось понять, существует ли более подходящий способ безопасного и эффективного получения множества элементов данных из структуры JSON-объекта в переменные shell?
Я действительно не вижу проблемы в выборе другого разделителя, например, табуляции. Это довольно традиционный выбор разделителя в Unix-приложениях обработки данных (по умолчанию для paste
и cut
, например). А jq
имеет оператор вывода @tsv
, который вы могли бы использовать:
jq -r '.customers[] | [.id, .firstname, .lastname] | @tsv' file |
while IFS=$'\t' read id first last
do
printf '%s - %s - %s\n' "$id" "$first" "$last"
done
Я бы использовал фильтр @tsv
:
jq -r '.customers[] | [.id, .firstname, .lastname] | @tsv' < data.json |
while IFS=$'\t' read -r id firstname lastname
do
printf '%b -- %b -- %b\n' "$id" "$firstname" "$lastname"
done
Будьте осторожны с пустыми полями, поскольку read
может “сломаться”; вам следует убедиться, что всегда выводится как минимум один символ на поле.
Примечание: %b
служит для декодирования escape-последовательностей @tsv
Ответ или решение
Извлечение связанных значений из массива JSON-объектов для последующей обработки в оболочке — частая задача при работе с данными. Контекст задачи таков: у нас есть JSON-объект, в котором содержатся данные о клиентах, и мы хотим извлечь связанные поля id
, firstname
и lastname
из каждого объекта массива в отдельные переменные оболочки. Однако проблема заключается в том, чтобы делать это эффективно и корректно даже в случае, если имя клиента состоит из нескольких слов. Давайте рассмотрим этот вопрос более подробно.
Теория
Для обработки JSON-объектов в скриптах оболочки часто используют инструмент jq
, который является легким и мощным процессором JSON. jq
позволяет осуществлять фильтрацию, трансформацию и извлечение данных из JSON-структур, предоставляя возможность легко работать с такими данными в Unix-окружении.
Обычно, когда данные просты и структурированы, стандартные подходы заключаются в линейном извлечении данных с использованием несложных фильтров. Однако когда данные становятся более сложными, например, включают в себя пробелы или другие специальные символы, необходимо применять более надежные методы для извлечения данных, чтобы избежать ошибок. Основной вызов в данной задаче заключается в том, чтобы извлекать несколько полей из JSON-объекта и корректно преобразовывать их в переменные оболочки, не теряя данные из-за некорректного разделения их на пробелы.
Пример
Рассматриваемый JSON выглядит следующим образом:
{
"customers": [
{
"id": 1234,
"firstname": "John",
"lastname": "Smith",
"other": "fields",
"are": "present",
"here": "etc."
},
{
"id": 2468,
"firstname": "Janet",
"lastname": "Green",
"other": "values",
"are": "probably",
"here": "maybe"
}
]
}
Для извлечения данных, как было предложено, мы можем воспользоваться следующим скриптом:
jq -r '.customers[] | [.id, .firstname, .lastname] | @tsv' < data.json |
while IFS=$'\t' read -r id firstname lastname
do
printf '%b -- %b -- %b\n' "$id" "$firstname" "$lastname"
done
Вышеописанный скрипт использует @tsv
фильтр jq
для вывода данных в формате, разделенном табуляцией. Это значит, что каждое значение будет разделено символом табуляции, что позволяет корректно различать значения, даже если они содержат пробелы, без необходимости сложной логики обработки. Команда read
используется здесь с модификатором IFS=$'\t'
для правильного разбора строк на основе символов табуляции.
Применение
-
Корректность и надежность: Применение
@tsv
оператора помогает избежать ошибок, связанных с пробелами или другими разделителями внутри полей данных. Это более надежное решение по сравнению с использованием произвольных символов, особенно когда существует возможность встречи двусоставных имен. -
Эффективность: Это решение позволяет сразу извлекать все необходимые данные за один проход, не вызывая
jq
многократно, что особенно важно при работе с большими файлами данных. Значительное сокращение числа операций над данными может существенно повлиять на скорость выполнения скрипта в процессе массовой обработки. -
Совместимость и стандарты: Использование разделителя табуляции соответствует стандартным практикам Unix-систем, где процедуры обработки данных с разделителями являются рутиной. Это делает решение совместимым с другими инструментами обработки данных, такими как
cut
,awk
, иsed
, которые также работают с табуляцией. -
Простота понимания и поддержки: Благодаря встроенным возможностям
jq
и стандартным возможностямbash
скрипт остается компактным и легко понимаемым, что упрощает его поддержку и обновление при изменении структуры данных.
Заключение
Таким образом, для эффективного и корректного извлечения связанных значений из массива JSON-объектов рекомендуется использовать мощь jq
в сочетании с обработкой табулированных данных в оболочке. Этот подход гарантирует высокую надежность выполнения даже в сложных случаях с просторечными именами или другими особенностями данных. Применение этого метода позволяет не только получить точные результаты, но и ощутимо ускоряет процесс обработки больших объемов данных.