it-swarm.com.ru

Создайте JSON, используя jq из разделенных трубами ключей и значений в bash

Я пытаюсь создать объект JSON из строки в Bash. Строка выглядит следующим образом. 

CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0

Выходные данные получены из команды docker stats, и моей конечной целью является публикация пользовательских метрик в aws cloudwatch. Я хотел бы отформатировать эту строку как JSON. 

{
    "CONTAINER":"nginx_container",
    "CPU%":"0.02%", 
    ....
}

Я уже использовал команду jq, и похоже, что она должна хорошо работать в этом случае, но я пока не смог найти хорошее решение. Кроме жесткого кодирования имен переменных и индексации с использованием sed или awk. Затем создать JSON с нуля. Мы ценим любые предложения. Благодарю. 

14
michael_65

Необходимое условие

Для всего перечисленного ниже предполагается, что ваш контент находится в переменной Shell с именем s:

s='CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0'

Что (современный JQ)

# thanks to @JeffMercado and @chepner for refinements, see comments
jq -Rn '
( input  | split("|") ) as $keys |
( inputs | split("|") ) as $vals |
[[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries
' <<<"$s"

Как (современный JQ)

Это требует очень нового (вероятно, 1,5?) jq для работы, и это большой кусок кода. Чтобы сломать это:

  • Использование -n не позволяет jq самостоятельно читать stdin, оставляя весь поток ввода доступным для чтения input и inputs - первый для чтения одной строки, а второй для чтения всех оставшихся строк. (-R для необработанного ввода вызывает чтение текстовых строк, а не объектов JSON).
  • С [$keys, $vals] | transpose[] мы генерируем пары [key, value] (в терминах Python, сжатие двух списков).
  • С {key:.[0],value:.[1]} мы превращаем каждую пару [key, value] в объект вида {"key": key, "value": value}
  • С from_entries мы объединяем эти пары в объекты, содержащие эти ключи и значения.

Что (Shell-Assisted)

Это будет работать со значительно более старым jq, чем приведенное выше, и является легко применяемым подходом для сценариев, в которых решение с собственным -jq может быть сложнее:

{
   IFS='|' read -r -a keys # read first line into an array of strings

   ## read each subsequent line into an array named "values"
   while IFS='|' read -r -a values; do

    # setup: positional arguments to pass in literal variables, query with code    
    jq_args=( )
    jq_query='.'

    # copy values into the arguments, reference them from the generated code    
    for idx in "${!values[@]}"; do
        [[ ${keys[$idx]} ]] || continue # skip values with no corresponding key
        jq_args+=( --arg "key$idx"   "${keys[$idx]}"   )
        jq_args+=( --arg "value$idx" "${values[$idx]}" )
        jq_query+=" | .[\$key${idx}]=\$value${idx}"
    done

    # run the generated command
    jq "${jq_args[@]}" "$jq_query" <<<'{}'
  done
} <<<"$s"

Как (Shell-Assisted)

Вызванная команда jq из приведенного выше похожа на:

jq --arg key0   'CONTAINER' \
   --arg value0 'nginx_container' \
   --arg key1   'CPU%' \
   --arg value1 '0.0.2%' \
   --arg key2   'MEMUSAGE/LIMIT' \
   --arg value2 '25.09MiB/15.26GiB' \
   '. | .[$key0]=$value0 | .[$key1]=$value1 | .[$key2]=$value2' \
   <<<'{}'

... передавая каждый ключ и значение вне диапазона (так, чтобы он обрабатывался как буквальная строка, а не анализировался как JSON), а затем ссылался на них по отдельности.


Результат

Любой из вышеперечисленных будет излучать:

{
  "CONTAINER": "nginx_container",
  "CPU%": "0.02%",
  "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
  "MEM%": "0.16%",
  "NETI/O": "0B/0B",
  "BLOCKI/O": "22.09MB/4.096kB",
  "PIDS": "0"
}

Зачем

Короче говоря: Потому что гарантированно генерировать действительный JSON в качестве вывода .

Рассмотрим следующее в качестве примера, который сломает более наивные подходы:

s='key ending in a backslash\
value "with quotes"'

Конечно, это неожиданные сценарии, но jq знает, как с ними справиться:

{
  "key ending in a backslash\\": "value \"with quotes\""
}

... в то время как реализация, которая не понимает строки JSON, может легко сгенерировать:

{
  "key ending in a backslash\": "value "with quotes""
}
32
Charles Duffy

json_template='{"CONTAINER":"%s","CPU%":"%s","MEMUSAGE/LIMIT":"%s", "MEM%":"%s","NETI/O":"%s","BLOCKI/O":"%s","PIDS":"%s"}' json_string=$(printf "$json_template" "nginx_container" "0.02%" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0") echo "$json_string"

Не используя jq, но можно использовать аргументы и окружение в значениях.

CONTAINER=nginx_container json_template='{"CONTAINER":"%s","CPU%":"%s","MEMUSAGE/LIMIT":"%s", "MEM%":"%s","NETI/O":"%s","BLOCKI/O":"%s","PIDS":"%s"}' json_string=$(printf "$json_template" "$CONTAINER" "$1" "25.09MiB/15.26GiB" "0.16%" "0B/0B" "22.09MB/4.096kB" "0") echo "$json_string"

1
NoamG

Вот решение, которое использует параметры -R и -s вместе с transpose :

   split("\n")                       # [ "CONTAINER...", "nginx_container|0.02%...", ...]
 | (.[0]    | split("|")) as $keys   # [ "CONTAINER", "CPU%", "MEMUSAGE/LIMIT", ... ]
 | (.[1:][] | split("|"))            # [ "nginx_container", "0.02%", ... ] [ ... ] ...
 | select(length > 0)                # (remove empty [] caused by trailing newline)
 | [$keys, .]                        # [ ["CONTAINER", ...], ["nginx_container", ...] ] ...
 | [ transpose[] | {(.[0]):.[1]} ]   # [ {"CONTAINER": "nginx_container"}, ... ] ...
 | add                               # {"CONTAINER": "nginx_container", "CPU%": "0.02%" ...
1
jq170727
JSONSTR=""
declare -a JSONNAMES=()
declare -A JSONARRAY=()
LOOPNUM=0

cat ~/newfile | while IFS=: read CONTAINER CPU MEMUSE MEMPC NETIO BLKIO PIDS; do
    if [[ "$LOOPNUM" = 0 ]]; then
        JSONNAMES=("$CONTAINER" "$CPU" "$MEMUSE" "$MEMPC" "$NETIO" "$BLKIO" "$PIDS")
        LOOPNUM=$(( LOOPNUM+1 ))
    else
        echo "{ \"${JSONNAMES[0]}\": \"${CONTAINER}\", \"${JSONNAMES[1]}\": \"${CPU}\", \"${JSONNAMES[2]}\": \"${MEMUSE}\", \"${JSONNAMES[3]}\": \"${MEMPC}\", \"${JSONNAMES[4]}\": \"${NETIO}\", \"${JSONNAMES[5]}\": \"${BLKIO}\", \"${JSONNAMES[6]}\": \"${PIDS}\" }"
    fi 
done

Возвращает:

{ "CONTAINER": "nginx_container", "CPU%": "0.02%", "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB", "MEM%": "0.16%", "NETI/O": "0B/0B", "BLOCKI/O": "22.09MB/4.096kB", "PIDS": "0" }
1
Nick Bull

Вы можете попросить Docker предоставить вам данные JSON.

docker stats --format "{{json .}}"

Подробнее об этом см .: https://docs.docker.com/config/formatting/

0
MatrixManAtYrService

Если вы начинаете с табличных данных, я думаю, что имеет больше смысла использовать то, что изначально работает с табличными данными, например sqawk , чтобы превратить их в json, а затем использовать jq для дальнейшей работы с ними.

echo 'CONTAINER|CPU%|MEMUSAGE/LIMIT|MEM%|NETI/O|BLOCKI/O|PIDS
nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0' \
        | sqawk -FS '[|]' -RS '\n' -output json 'select * from a' header=1 \
        | jq '.[] | with_entries(select(.key|test("^a.*")|not))'

    {
      "CONTAINER": "nginx_container",
      "CPU%": "0.02%",
      "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
      "MEM%": "0.16%",
      "NETI/O": "0B/0B",
      "BLOCKI/O": "22.09MB/4.096kB",
      "PIDS": "0"
    }

Без jqsqawk выдает слишком много:

[
  {
    "anr": "1",
    "anf": "7",
    "a0": "nginx_container|0.02%|25.09MiB/15.26GiB|0.16%|0B/0B|22.09MB/4.096kB|0",
    "CONTAINER": "nginx_container",
    "CPU%": "0.02%",
    "MEMUSAGE/LIMIT": "25.09MiB/15.26GiB",
    "MEM%": "0.16%",
    "NETI/O": "0B/0B",
    "BLOCKI/O": "22.09MB/4.096kB",
    "PIDS": "0",
    "a8": "",
    "a9": "",
    "a10": ""
  }
]
0
MatrixManAtYrService