Reputation: 2752
My question is similar to this question but I need something more to be done and can't quite figure out how to do.
This is my JSON string
{
"value": "1"
}
{
"value": "3"
}
{
"value": "4"
}
I need to write a script in BASH to output
In the above example, 2 is missing from the json array. And the script should return 2. If 2 is present in the array it should return 5.
I think I may be able to write the logic to increment the number using a while loop in bash, but I am stuck at this point where I can't figure out how to convert this JSON string to bash array with just the value.
Below is the exact command that I used to get the JSON output. This is AWS CLI command to get all the instances from AWS that has a specific TAG.
readarray -t arr < <(aws ec2 describe-instances --region=us-east-1 --filters --filters "Name=tag:NodeType,Values=worker" --query "Reservations[].Instances[].Tags[]" | jq -r '.[] | select(.Key == "NodeNumber") | {value: .Value}')
printf '%s\n' "${arr[@]}"
The above returns me
{
"value": "1"
}
{
"value": "3"
}
{
"value": "4"
}
However, I need to get just the VALUE field "value" as a bash array
Upvotes: 8
Views: 14295
Reputation: 18697
To convert your JSON to a bash
array, with help of jq
:
$ readarray -t arr < <(jq '.value' file)
$ printf '%s\n' "${arr[@]}"
"1"
"3"
"4"
To fix your expanded example (the exact command), just don't use object construction {value: .Value}
, but instead only .Value
:
$ readarray -t arr < <(aws ec2 describe-instances --region=us-east-1 --filters --filters "Name=tag:NodeType,Values=worker" --query "Reservations[].Instances[].Tags[]" | jq -r '.[] | select(.Key == "NodeNumber") | .Value')
$ printf '%s\n' "${arr[@]}"
1
3
4
Notice the lack of double quotes, since the -r
option now prints only raw string values, not raw JSON Objects.
After you get arr
populated with values like this, you can easily iterate over it and perform tests, just as you described in your question.
Upvotes: 5
Reputation: 2356
This might give you some ideas:
#!/bin/bash
complete=true
while read value;do
n=${n:-$value}
if (( value != n ));then
complete=false
echo $n
break
fi
let n++
done < <(jq -r '.value' json_file)
# or: done < <( command_that_outputs_json | jq -r '.value' )
$complete && echo $n
Upvotes: 0
Reputation: 84393
Given your raw data stored as a string in a json variable, perhaps with a here-document:
json=$(
cat <<- EOF
{
"value": "1"
}
{
"value": "3"
}
{
"value": "4"
}
EOF
)
Bash itself will do a reasonable job of prettifying it:
$ echo $json
{ "value": "1" } { "value": "3" } { "value": "4" }
There's more than one way to do this. Two of the more obvious ways are to use use jq or grep to extract the values into a Bash array with the shell's simple array notation:
values=( `echo $json | jq .value` )
echo "${values[@]}"
"1" "3" "4"
unset values
values=$(egrep -o '[[:digit:]]+' <<< "$json")
echo "${values[@]}"
1
3
4
There are certainly other ways to accomplish this task, but this seems like the low-hanging fruit. Your mileage may vary.
The main thing to remember about Bash arrays is that they need to use expansions such as "${foo[@]}"
when quoted, or ${bar[*]}
when unquoted, if you want to use them for loops rather than indexing into them directly. Once they're in an array, though, you can access the entire array easily as long as you understand the different expansions and quoting rules.
Upvotes: 2
Reputation: 116870
Using the "-s" command-line option of jq, the following meets the requirements as I understand them, while generalizing them as well:
map(.value | tonumber)
| unique
| (1+max - min) as $length
| if $length > length then ([range(min;max+1)] - .)[]
else max+1
end
If you just want the least missing value, then replace the '[]' on the 'if' line by [0]
,
Upvotes: 1