Создайте 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 раньше, и кажется, что в этом случае она должна хорошо работать, но я пока не смог найти хорошее решение. За исключением имен переменных и индексирования hardcoding с помощью sed или awk. Затем создайте json с нуля. Мы ценим любые предложения. Благодарю.
Ответы
Ответ 1
Необходимое условие
Для всего ниже, он предположил, что ваш контент находится в переменной оболочки с именем 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
мы объединяем эти пары в объекты, содержащие эти ключи и значения.
Что (с оболочкой)
Это будет работать со значительно более старым jq
, чем указано выше, и является легко принятым подходом к сценариям, где решение native-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"
Как (с помощью оболочки)
Вызываемая команда 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""
}
Ответ 2
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" }
Ответ 3
Вот решение, которое использует параметры -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%" ...
Ответ 4
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"
Ответ 5
Если вы начинаете с табличных данных, я думаю, что имеет смысл использовать что-то, что изначально работает с табличными данными, например 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"
}
Без jq
sqawk
выдает слишком много:
[
{
"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": ""
}
]
Ответ 6
Вы можете попросить Docker предоставить вам данные JSON.
docker stats --format "{{json .}}"
Подробнее об этом см.: https://docs.docker.com/config/formatting/
Ответ 7
Я знаю, что это старый пост, но инструмент, который вы ищете, называется jo
: https://github.com/jpmens/jo
Быстрый и простой пример:
$ jo my_variable="simple"
{"my_variable":"simple"}
Немного сложнее
$ jo -p name=jo n=17 parser=false
{
"name": "jo",
"n": 17,
"parser": false
}
Добавить массив
$ jo -p name=jo n=17 parser=false my_array=$(jo -a {1..5})
{
"name": "jo",
"n": 17,
"parser": false,
"my_array": [
1,
2,
3,
4,
5
]
}
Я сделал несколько довольно сложных вещей с Джо, и приятно то, что вам не нужно беспокоиться о развертывании собственного решения, беспокоясь о возможности создания недействительного json.