Итерация по массиву JSON в сценарии Shell

У меня есть данные JSON следующим образом в файле data.json

[
  {"original_name":"pdf_convert","changed_name":"pdf_convert_1"},
  {"original_name":"video_encode","changed_name":"video_encode_1"},
  {"original_name":"video_transcode","changed_name":"video_transcode_1"}
]

Я хочу перебирать массив и извлекать значение для каждого элемента в цикле. Я видел jq. Мне трудно использовать его для повторения. Как я могу это сделать?


person kosta    schedule 27.11.2015    source источник
comment
Похоже, что у jq есть команда foreach, вы пробовали это?   -  person Kevin    schedule 27.11.2015
comment
Честно говоря, я думаю, что вы были бы гораздо более удовлетворены простым скриптом Python. Вы даже можете встроить его в сценарий оболочки, используя синтаксис heredoc.   -  person 5gon12eder    schedule 27.11.2015
comment
Можете ли вы привести пример встраивания python в сценарий оболочки?   -  person kosta    schedule 27.11.2015


Ответы (5)


Просто используйте фильтр, который будет возвращать каждый элемент в массиве. Затем зациклите результаты, просто убедитесь, что вы используете опцию компактного вывода (-c), чтобы каждый результат помещался в одну строку и обрабатывался как один элемент в цикле.

jq -c '.[]' input.json | while read i; do
    # do stuff with $i
done
person Jeff Mercado    schedule 27.11.2015
comment
Цикл for перебирает слова, разделенные пробелами, а не строки. - person chepner; 03.12.2015
comment
Да, вы правы, хотя в этом конкретном случае все было бы в порядке, поскольку ни в одном из объектов не было пробелов. Но идея осталась прежней, петлевой механизм, вероятно, был неправильным выбором. - person Jeff Mercado; 03.12.2015
comment
jq выводит поток, так что вы не идете построчно или по пунктам. - person knt5784; 22.06.2019
comment
Если ваш вывод содержит пробелы, вам нужно установить IFS на новую строку, например, с помощью Bash IFS=$'\n'. - person Andrey Kaipov; 25.08.2020

jq имеет параметр форматирования оболочки: @sh.

Вы можете использовать следующее для форматирования данных json в качестве параметров оболочки:

cat data.json | jq '. | map([.original_name, .changed_name])' | jq @sh

Вывод будет выглядеть так:

"'pdf_convert' 'pdf_convert_1'"
"'video_encode' 'video_encode_1'",
"'video_transcode' 'video_transcode_1'"

Чтобы обработать каждую строку, нам нужно сделать пару вещей:

  • Установите цикл for в bash для чтения всей строки, а не остановки на первом пробеле (поведение по умолчанию).
  • Удалите двойные кавычки из каждой строки, чтобы каждое значение можно было передать в качестве параметра функции, которая обрабатывает каждую строку.

Чтобы прочитать всю строку на каждой итерации цикла for bash, установите переменную IFS, как описано в этот ответ.

Чтобы избавиться от двойных кавычек, мы запустим его через интерпретатор оболочки bash, используя xargs:

stripped=$(echo $original | xargs echo)

Собрав все вместе, мы имеем:

#!/bin/bash

function processRow() {
  original_name=$1
  changed_name=$2

  # TODO
}

IFS=$'\n' # Each iteration of the for loop should read until we find an end-of-line
for row in $(cat data.json | jq '. | map([.original_name, .changed_name])' | jq @sh)
do
  # Run the row through the shell interpreter to remove enclosing double-quotes
  stripped=$(echo $row | xargs echo)

  # Call our function to process the row
  # eval must be used to interpret the spaces in $stripped as separating arguments
  eval processRow $stripped
done
unset IFS # Return IFS to its original value
person Mashmagar    schedule 12.02.2019
comment
Вы можете использовать флаг --raw-output или -r, чтобы исключить двойные кавычки, вместо того, чтобы «удалять двойные кавычки», заменив jq @sh на jq -r @sh - person Cinderhaze; 11.03.2019

Используя возможности массивов Bash, вы можете сделать что-то вроде:

# read each item in the JSON array to an item in the Bash array
readarray -t my_array < <(jq -c '.[]' input.json)

# iterate through the Bash array
for item in "${my_array[@]}"; do
  original_name=$(jq '.original_name' <<< "$item")
  changed_name=$(jq '.changed_name' <<< "$item")
  # do your stuff
done
person felipecrs    schedule 21.05.2021
comment
Сила массивов Bash! ⚡️ - Это слишком, чувак. - person user14492; 30.06.2021
comment
примечание для пользователей macOS — это не будет работать «из коробки» из-за того, что Apple придерживается более старой версии bash из-за лицензирования (в настоящее время v3.2.57). вы можете использовать homebrew для получения последней версии. Вам нужно будет установить более новую версию в качестве оболочки по умолчанию или настроить свой скрипт на явное использование с помощью shebang. - person Baldy; 28.07.2021
comment
Хорошо знать! Должно быть, поэтому macOS так переключилась на ZSH. - person felipecrs; 29.07.2021

Попробуйте построить его вокруг этого примера. (Источник: оригинальный сайт)

Пример:

jq '[foreach .[] as $item ([[],[]]; if $item == null then [[],.[0]]     else [(.[0] + [$item]),[]] end; if $item == null then .[1] else empty end)]'

Input [1,2,3,4,null,"a","b",null]

Output [[1,2,3,4],["a","b"]]

person touchStone    schedule 27.11.2015
comment
Первоначальный вопрос расплывчатый, но я не думаю, что foreach вообще необходим для того, чего хочет пользователь. - person chepner; 03.12.2015

В более раннем ответе в этой теме предлагалось использовать jq foreach, но это может быть намного сложнее, чем необходимо, особенно с учетом поставленной задачи. В частности, foreachreduce) предназначены для определенных случаев, когда вам нужно накапливать результаты.

Во многих случаях (включая некоторые случаи, когда в конечном итоге необходим шаг сокращения) лучше использовать .[] или map(_). Последнее — это просто еще один способ записи [.[] | _] поэтому, если вы собираетесь использовать jq, очень полезно понимать, что .[] просто создает поток значений. Например, [1,2,3] | .[] создает поток из трех значений.

Чтобы взять простой пример уменьшения карты, предположим, что вы хотите найти максимальную длину массива строк. Одним из решений будет [ .[] | length] | max.

person peak    schedule 27.11.2015