Can you separate distinct JSON attributes into two files using jq? - json

I am following this tutorial from Vault about creating your own certificate authority. I'd like to separate the response (change the output to API call using cURL to see the response) into two distinct files, one file possessing the certificate and issuing_ca attributes, the other file containing the private_key. The tutorial is using jq to parse JSON objects, but my unfamiliarity with jq isn't helpful here, and most searches are returning info on how to merge JSON using jq.
I've tried running something like
vault write -format=json pki_int/issue/example-dot-com \
common_name="test.example.com" \
ttl="24h" \
format=pem \
jq -r '.data.certificate, .data.issuing_ca > test.cert.pem \
jq -r '.data.private_key' > test.key.pem
or
vault write -format=json pki_int/issue/example-dot-com \
common_name="test.example.com" \
ttl="24h" \
format=pem \
| jq -r '.data.certificate, .data.issuing_ca > test.cert.pem \
| jq -r '.data.private_key' > test.key.pem
but no dice.

It is not an issue with jq invocation, but the way the output files get written. Per your usage indicated, after writing the file test.cert.pem, the contents over the read end of the pipe (JSON output) is no longer available to extract the private_key contents.
To duplicate the contents over at the write end of pipe, use tee along with process substitution. The following should work on bash/zsh or ksh93 and not on POSIX bourne shell sh
vault write -format=json pki_int/issue/example-dot-com \
common_name="test.example.com" \
ttl="24h" \
format=pem \
| tee >( jq -r '.data.certificate, .data.issuing_ca' > test.cert.pem) \
>(jq -r '.data.private_key' > test.key.pem) \
>/dev/null
See this in action
jq -n '{data:{certificate: "foo", issuing_ca: "bar", private_key: "zoo"}}' \
| tee >( jq -r '.data.certificate, .data.issuing_ca' > test.cert.pem) \
>(jq -r '.data.private_key' > test.key.pem) \
>/dev/null
and now observe the contents of both the files.

You could abuse jq's ability to write to standard error (version 1.6 or later) separately from standard output.
vault write -format=json pki_int/issue/example-dot-com \
common_name="test.example.com" \
ttl="24h" \
format=pem \
| jq -r '.data as $f | ($f.private_key | stderr) | ($f.certificate, $f.issuing_ca)' > test.cert.pem 2> test.key.pem

There's a general technique for this type of problem that is worth mentioning
because it has minimal prerequisites (just jq and awk), and because
it scales well with the number of files. Furthermore it is quite efficient in that only one invocation each of jq and awk is needed. The idea is to setup a pipeline of the form: jq ... | awk ...
There are many variants
of the technique but in the present case, the following would suffice:
jq -rc '
.data
| "test.cert.pem",
"\t\(.certificate)",
"\t\(.issuing_ca)",
"test.key.pem",
"\t\(.private_key)"
' | awk -F\\t 'NF == 1 {fn=$1; next} {print $2 > fn}'
Notice that this works even if the items of interest are strings with embedded tabs.

Related

Parse JSON data in variables using JQ

I have some problems with my script which collects data from a JSON, stores them in variables which are then used for a CURL request. I need to build a CURL request for EACH JSON entry.
My problem is that I would like to pass parameters to the CURL request, one by one.
I was thinking about a for-loop but this won't be actually the right workaround.
This because
ruleId=$($whitelist | jq -r '.[].ruleId')
gives:
10055
10098
This can not be interpreted correctly from CURL.
So the question is, how can I pass variables in a proper manner, in a sort of iteration, using JQ? Again, I need to do single calls using CURLs for each entry in the JSON file.
Code:
$!/bin/sh
#set -e
# Info to test this script
# 1. Start docker
# 2. Run this command from terminal: docker run -u zap -p 8080:8080 -i owasp/zap2docker-stable zap.sh -daemon -host 0.0.0.0 -port 8080 -config api.disablekey=true -config api.addrs.addr.name=.* -config api.addrs.addr.regex=true
# 2. Get the JSON from api (test for now)
# 3. bash filter.sh
# 4. Check for filter to be set with browser http://localhost:8080/UI/alertFilter/ => view global filter
curl -s 'https://api.npoint.io/c29e3a68be632f73fc22' > whitelist_tmp.json
whitelist="cat whitelist_tmp.json"
listlength=$(jq '. | length' $whitelist)
ruleId=$($whitelist | jq -r '.[].ruleId')
alert=$($whitelist | jq -r '.[].alertName')
newLevel=$($whitelist | jq -r '.[].newLevel')
url=$($whitelist | jq -r '.[].url | .[]')
urlIsRegex=$($whitelist | jq -r '.[].urlIsRegex')
enabled=$($whitelist | jq -r '.[].enabled')
parameter=$($whitelist | jq -r '.[].parameter')
evidence=$($whitelist | jq -r '.[].evidence')
echo "Setting Rule for: $ruleId"
echo "$(curl --data-urlencode "ruleId=$ruleId" --data-urlencode "newLevel=$newLevel" --data-urlencode "url=$url" --data-urlencode "urlIsRegex=$urlIsRegex" --data-urlencode "enabled=$enabled" --data-urlencode "parameter=$parameter" --data-urlencode "evidence=$evidence" "http://localhost:8090/JSON/alertFilter/action/addGlobalAlertFilter")"
You can use bash arrays to store your values:
ruleId=($($whitelist | jq -r '.[].ruleId'))
alert=($($whitelist | jq -r '.[].alertName'))
...
and then iterate over them. Example:
for (( i = 0; i < "${#ruleId[#]}"; i++ )); do
id="${ruleId[i]}"
al="${alert[i]}"
...
echo "$(curl --data-urlencode "ruleId=$id" ...
done
This works if and only if the values returned by your commands are single words (no spaces in them) or they are properly quoted. If you have more complex values you cannot simply assign them to an array with array=($(command)). You would get more cells than values in your array.
Pulling the comments from your previous question:
whitelist="whitelist_tmp.json"
listlength=$(jq '. | length' "${whitelist}")
mapfile -t rule < <(jq -r '.[].ruleId' "${whitelist}")
mapfile -t alert < <(jq -r '.[].cwalertName' "${whitelist}")
mapfile -t level < <(jq -r '.[].newLevel' "${whitelist}")
mapfile -t url < <(jq -r '.[].url | .[]' "${whitelist}")
mapfile -t regex < <(jq -r '.[].urlIsRegex' "${whitelist}")
mapfile -t parameter < <(jq -r '.[].parameter' "${whitelist}")
mapfile -t evidence < <(jq -r '.[].evidence' "${whitelist}")
for ((i=0; i<${listlength}; i++))
do
curl ... "${rule[$i]}" ... "${alert[$i]}" ...
done
The mapfile should maintain embedded white space in values returned by jq.

use curl/bash command in jq

I am trying to get a list of URL after redirection using bash scripting. Say, google.com gets redirected to http://www.google.com with 301 status.
What I have tried is:
json='[{"url":"google.com"},{"url":"microsoft.com"}]'
echo "$json" | jq -r '.[].url' | while read line; do
curl -LSs -o /dev/null -w %{url_effective} $line 2>/dev/null
done
So, is it possible for us to use commands like curl inside jq for processing JSON objects.
I want to add the resulting URL to existing JSON structure like:
[
{
"url": "google.com",
"redirection": "http://www.google.com"
},
{
"url": "microsoft.com",
"redirection": "https://www.microsoft.com"
}
]
Thank you in advance..!
curl is capable of making multiple transfers in a single process, and it can also read command line arguments from a file or stdin, so, you don't need a loop at all, just put that JSON into a file and run this:
jq -r '"-o /dev/null\nurl = \(.[].url)"' file |
curl -sSLK- -w'%{url_effective}\n' |
jq -R 'fromjson | map(. + {redirection: input})' file -
This way only 3 processes will be spawned for the whole task, instead of n + 2 where n is the number of URLs.
I would generate a dictionary with jq per url and slurp those dictionaries into the final list with jq -s:
json='[{"url":"google.com"},{"url":"microsoft.com"}]'
echo "$json" | jq -r '.[].url' | while read url; do
redirect=$(curl -LSs \
-o /dev/null \
-w '%{url_effective}' \
"${url}" 2>/dev/null)
jq --null-input --arg url "${url}" --arg redirect "${redirect}" \
'{url:$url, redirect: $redirect}'
done | jq -s
Alternative (first) solution:
You can output the url and the effective_url as tab separated data and create the output json with jq:
json='[{"url":"google.com"},{"url":"microsoft.com"}]'
echo "$json" | jq -r '.[].url' | while read line; do
prefix="${line}\t"
curl -LSs -o /dev/null -w "${prefix}"'%{url_effective}'"\n" "$line" 2>/dev/null
done | jq -r --raw-input 'split("\t")|{"url":.[0],"redirection":.[1]}'
Both solutions will generate valid json, independently of whatever characters the url/effective_url might contain.
Trying to keep this in JSON all the way is pretty cumbersome. I would simply try to make Bash construct a new valid JSON fragment inside the loop.
So in other words, if $url is the URL and $redirect is where it redirects to, you can do something like
printf '{"url": "%s", "redirection": "%s"}\n' "$url" "$redirect"
to produce JSON output from these strings. So tying it all together
jq -r '.[].url' <<<"$json" |
while read -r url; do
printf '{"url:" "%s", "redirection": "%s"}\n' \
"$url" "$(curl -LSs -o /dev/null -w '%{url_effective}' "$url")"
done |
jq -s
This is still pretty brittle; in particular, if either of the printf input strings could contain a literal double quote, that should properly be escaped.

JQ JSON Creation

New to JSON creation, decided to try JQ from this question. However, when I tried implementing it, all I received was the help page for JQ and these types of errors: ./test.sh: line 43: --: command not found for the last three lines.
#!/bin/bash
echo Starting
sensorType="XXX"
sensorLocation="XXX"
sensorCommand="XXXXX"
# Hardware information.
systemTime=$(date +%d-%m-%Y_%H:%M:%S)
kernalName=$(uname -s)
nodeName=$(uname -i)
kernalRelease=$(uname -r)
kernalVersion=$(uname -v)
machine=$(uname -m)
processor=$(uname -p)
hardwarePlatform=$(uname -i)
operatingSystem=$(uname -o)
timeup=$(uptime)
# Software information.
softVersion=$(XXX version)
JSON_STRING=$( jq -n \
-- arg systemTime "$systemTime" \
-- arg sensorType "$sensorType" \
-- arg sensorLocation "$sensorLocation" \
-- arg sensorCommand "$sensorCommand" \
-- arg kernalName "$kernalName" \
-- arg nodeName "$nodeName" \
-- arg kernalRelease "$kernalRelease" \
-- arg kernalVersion "$kernalVersion" \
-- arg machine "$machine" \
-- arg processor "$processor"
-- arg hardwarePlatform "$hardwarePlatform" \
-- arg operatingSystem "$operatingSystem" \
-- arg timeup "$timeup" \
-- arg softVersion "$softVersion" \
'{systemTime: $systemTime, sensorType: $sensorType, sensorLocation: $sensorLocation, kernalName: $kernalName, nodeName: $nodeName, kernalRelease: $kernalRelease, machine: $machine, processor: $processor, hardwarePlatform: $hardwarePlatform, operatingSystem: $operatingSystem, timeup: $timeup, softVersion: $softVersion }' )
echo $JSON_STRING
Not sure if this is the most efficient way of using JQ or if there is a better way to implement it. But I would love to hear if there is a more efficient / easier way to accomplish this.
Use an array to store the arguments before calling jq; it's easier to spread the arguments across multiple lines in an array assignment, as the backslashes aren't necessary.
jq_args=(
--arg systemTime "$systemTime"
--arg sensorType "$sensorType"
--arg sensorLocation "$sensorLocation"
--arg sensorCommand "$sensorCommand"
--arg kernalName "$kernalName"
--arg nodeName "$nodeName"
--arg kernalRelease "$kernalRelease"
--arg kernalVersion "$kernalVersion"
--arg machine "$machine"
--arg processor "$processor"
--arg hardwarePlatform "$hardwarePlatform"
--arg operatingSystem "$operatingSystem"
--arg timeup "$timeup"
--arg softVersion "$softVersion"
)
JSON_STRING=$( jq -n "${jq_args[#]}" '{
systemTime: $systemTime,
sensorType: $sensorType,
sensorLocation: $sensorLocation,
kernalName: $kernalName,
nodeName: $nodeName,
kernalRelease: $kernalRelease,
machine: $machine,
processor: $processor,
hardwarePlatform: $hardwarePlatform,
operatingSystem: $operatingSystem,
timeup: $timeup,
softVersion: $softVersion
}' )
If you are using a version of bash that supports associative arrays, you can further simply the building of jq_args:
declare -A x
x=([systemTime]="$systemTime"
[sensorType]="$sensorType"
# etc
)
declare -a jq_args
for k in "${!x[#]}"; do
jq_args+=(--arg "$k" "${x[$k]}")
done
JSON_STRING=$( jq -n "${jq_args[#]}" ... )
The first problem, as #Inian pointed out, is that there should be no space between "--" and "arg".
The second problem is that there should be no spaces after the backslash when it is used (as here) for line-continuation: for the backslash to serve as a line-continuation character, it must escape (i.e. immediately precede) the newline.
Otherwise, except possibly for some oddities such as $(XXX version), you should be good to go, which is not to say there aren't better ways to create the JSON object. See the next section for an illustration of an alternative approach.
Illustration of an alternative approach
The following approach can be used even if the keys and/or values contain control characters:
FMT="%s\0%s\0"
(
printf $FMT systemTime $(date +%d-%m-%Y_%H:%M:%S)
printf $FMT newline "$(echo a; echo b)"
) | jq -sR '[split("\u0000") | range(0;length;2) as $i | {(.[$i]): .[$i + 1]}] | add'
If it is known that no keys or values contain literal newline characters, the following variant, which has the main advantage of not requiring the "-s" option, could be used:
(
echo systemTime
date +%d-%m-%Y_%H:%M:%S
echo uname
uname -a
echo softVersion
echo XXX version
) | jq -nR '[inputs as $key | {($key): input}] | add'
Not sure if this is the most efficient way of using JQ
Here are two alternative approaches. The first is based on the TSV format, and the second the CSV format.
Using the TSV format
The following assumes that neither literal tabs nor literal newlines appear within keys or values.
FMT="%s\t%s\n"
(
printf $FMT systemTime $(date +%d-%m-%Y_%H:%M:%S)
printf $FMT uname "$(uname -a)"
printf $FMT softVersion "XXX version"
) | jq -nR '[inputs | split("\t") | {(.[0]): .[1]}] | add'
Using a CSV-to-JSON utility
The approach illustrated here is probably mainly of interest if one starts with a CSV file of key-value pairs.
In the following, we'll use the excellent csv2json utility at https://github.com/fadado/CSV There are actually two executables provided there. Both convert each CSV row to a JSON array. We'll use extended symlinked to csv2json.
(
echo systemTime, $(date +%d-%m-%Y_%H:%M:%S)
echo uname, "$(uname -a)"
echo softVersion, XXX version
) | csv2json
| jq -n '[inputs as [$key, $value] | {($key): $value}] | add'
If you have all the values as environment variables, you could just utilize the env object to get the values. The syntax then becomes trivial.
systemTime=$(date +%d-%m-%Y_%H:%M:%S) \
kernalName=$(uname -s) \
nodeName=$(uname -i) \
kernalRelease=$(uname -r) \
kernalVersion=$(uname -v) \
machine=$(uname -m) \
processor=$(uname -p) \
hardwarePlatform=$(uname -i) \
operatingSystem=$(uname -o) \
timeup=$(uptime) \
jq -n 'env | {
systemTime,
sensorType,
sensorLocation,
kernalName,
nodeName,
kernalRelease,
machine,
processor,
hardwarePlatform,
operatingSystem,
timeup,
softVersion
}'

Selection of multiple json keys using jq

As a newbee to bash and jq, I was trying to download several urls from a json file using jq command in bash scripts.
My items.json file looks like this :
[
{"title" : [bob], "link" :[a.b.c]},
{"title" : [alice], "link" :[d.e.f]},
{"title" : [carol], "link" :[]}
]
what I was initially doing was just filter the non-empty link and put them in an array and then download the array:
#!/bin/bash
lnk=( $(jq -r '.[].link[0] | select (.!=null)' items.json) )
for element in ${lnk[#]}
do
wget $element
done
But the problem of this approach is that all the files downloaded use the link as the file names.
I wish to filter json file but still keeps the title name with the link so that i can rename the file in the wget command. But I dont have any idea on what structure should I use here. So how can i keep the title to in the filter and use it after?
You can use this:
IFS=$'\n' read -d '' -a titles < <(jq -r '.[] | select (.link[0]!=null) | .title[0]' items.json);
IFS=$'\n' read -d '' -a links < <(jq -r '.[] | select (.link[0]!=null) | .link[0]' items.json);
Then you can iterate over arrays "${title[#]}" & ${links[#]}...
for i in ${!titles[#]}; do
wget -O "${titles[i]}" "${links[#]}"
done
EDIT: Easier & safer approach:
jq -r '.[] | select (.link[0]!=null) | #sh "wget -O \(.title[0]) \(.link[0])"' items.json | bash
Here is a bash script demonstrating reading the result of a jq filter into bash variables.
#!/bin/bash
jq -M -r '
.[]
| select(.link[0]!=null)
| .title[0], .link[0]
' items.json | \
while read -r title; read -r url; do
echo "$title: $url" # replace with wget command
done

Shell Script CURL JSON value to variable

I was wondering how to parse the CURL JSON output from the server into variables.
Currently, I have -
curl -X POST -H "Content: agent-type: application/x-www-form-urlencoded" https://www.toontownrewritten.com/api/login?format=json -d username="$USERNAME" -d password="$PASSWORD" | python -m json.tool
But it only outputs the JSON from the server and then have it parsed, like so:
{
"eta": "0",
"position": "0",
"queueToken": "6bee9e85-343f-41c7-a4d3-156f901da615",
"success": "delayed"
}
But how do I put - for example the success value above returned from the server into a variable $SUCCESS and have the value as delayed & have queueToken as a variable $queueToken and 6bee9e85-343f-41c7-a4d3-156f901da615 as a value?
Then when I use-
echo "$SUCCESS"
it shows this as the output -
delayed
And when I use
echo "$queueToken"
and the output as
6bee9e85-343f-41c7-a4d3-156f901da615
Thanks!
Find and install jq (https://stedolan.github.io/jq/). jq is a JSON parser. JSON is not reliably parsed by line-oriented tools like sed because, like XML, JSON is not a line-oriented data format.
In terms of your question:
source <(
curl -X POST -H "$content_type" "$url" -d username="$USERNAME" -d password="$PASSWORD" |
jq -r '. as $h | keys | map(. + "=\"" + $h[.] + "\"") | .[]'
)
The jq syntax is a bit weird, I'm still working on it. It's basically a series of filters, each pipe taking the previous input and transforming it. In this case, the end result is some lines that look like variable="value"
This answer uses bash's "process substitution" to take the results of the jq command, treat it like a file, and source it into the current shell. The variables will then be available to use.
Here's an example of Extract a JSON value from a BASH script
#!/bin/bash
function jsonval {
temp=`echo $json | sed 's/\\\\\//\//g' | sed 's/[{}]//g' | awk -v k="text" '{n=split($0,a,","); for (i=1; i<=n; i++) print a[i]}' | sed 's/\"\:\"/\|/g' | sed 's/[\,]/ /g' | sed 's/\"//g' | grep -w $prop`
echo ${temp##*|}
}
json=`curl -s -X GET http://twitter.com/users/show/$1.json`
prop='profile_image_url'
picurl=`jsonval`
`curl -s -X GET $picurl -o $1.png`
A bash script which demonstrates parsing a JSON string to extract a
property value. The script contains a jsonval function which operates
on two variables, json and prop. When the script is passed the name of
a twitter user it attempts to download the user's profile picture.
You could use perl module on command line:
1st, ensure they is installed, under debian based, you could
sudo apt-get install libjson-xs-perl
But for other OS, you could install perl modules via CPAN (the Comprehensive Perl Archive Network):
cpan App::cpanminus
cpan JSON::XS
Note: You may have to run this with superuser privileges.
then:
curlopts=(-X POST -H
"Content: apent-type: application/x-www-form-urlencoded"
-d username="$USERNAME" -d password="$PASSWORD")
curlurl=https://www.toontownrewritten.com/api/login?format=json
. <(
perl -MJSON::XS -e '
$/=undef;my $a=JSON::XS::decode_json <> ;
printf "declare -A Json=\047(%s)\047\n", join " ",map {
"[".$_."]=\"".$a->{$_}."\""
} qw|queueToken success eta position|;
' < <(
curl "${curlopts[#]}" $curlurl
)
)
The line qw|...| let you precise which variables you want to be driven... This could be replaced by keys $a, but could have to be debugged as some characters is forbiden is associative arrays values names.
echo ${Json[queueToken]}
6bee9e85-343f-41c7-a4d3-156f901da615
echo ${Json[eta]}
0