I am trying to create a json object from a string in bash. The string is as follows.
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
The output is from docker stats command and my end goal is to publish custom metrics to aws cloudwatch. I would like to format this string as json.
{
"CONTAINER":"nginx_container",
"CPU%":"0.02%",
....
}
I have used jq command before and it seems like it should work well in this case but I have not been able to come up with a good solution yet. Other than hardcoding variable names and indexing using sed or awk. Then creating a json from scratch. Any suggestions would be appreciated. Thanks.
Here is a solution which uses the
-R
and-s
options along withtranspose
:If you're starting with tabular data, I think it makes more sense to use something that works with tabular data natively, like sqawk to make it into json, and then use jq work with it further.
Without
jq
,sqawk
gives a bit too much:I know this is an old post, but the tool you seek is called
jo
: https://github.com/jpmens/joA quick and easy example:
A little more complex
Add an array
I've made some pretty complex stuff with jo and the nice thing is that you don't have to worry about rolling your own solution worrying about the possiblity of making invalid json.
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"
Not using jq but possible to use args and environment in values.
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"
Prerequisite
For all of the below, it's assumed that your content is in a shell variable named
s
:What (modern jq)
How (modern jq)
This requires very new (probably 1.5?)
jq
to work, and is a dense chunk of code. To break it down:-n
preventsjq
from reading stdin on its own, leaving the entirety of the input stream available to be read byinput
andinputs
-- the former to read a single line, and the latter to read all remaining lines. (-R
, for raw input, causes textual lines rather than JSON objects to be read).[$keys, $vals] | transpose[]
, we're generating[key, value]
pairs (in Python terms, zipping the two lists).{key:.[0],value:.[1]}
, we're making each[key, value]
pair into an object of the form{"key": key, "value": value}
from_entries
, we're combining those pairs into objects containing those keys and values.What (shell-assisted)
This will work with a significantly older
jq
than the above, and is an easily adopted approach for scenarios where a native-jq
solution can be harder to wrangle:How (shell-assisted)
The invoked
jq
command from the above is similar to:...passing each key and value out-of-band (such that it's treated as a literal string rather than parsed as JSON), then referring to them individually.
Result
Either of the above will emit:
Why
In short: Because it's guaranteed to generate valid JSON as output.
Consider the following as an example that would break more naive approaches:
Sure, these are unexpected scenarios, but
jq
knows how to deal with them:...whereas an implementation that didn't understand JSON strings could easily end up emitting:
You can ask docker to give you JSON data in the first place
For more on this, see: https://docs.docker.com/config/formatting/