8

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

  • 2 - If the sequence is missing a number
  • 5 - If the sequence isn't missing a number

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

6
  • Do you know which value are you looking for? Commented Aug 6, 2017 at 17:32
  • In the above example, I am expecting to get 2. Commented Aug 6, 2017 at 17:35
  • However, if the example is 1,2,3,4 then I should be getting 5 as the output of my bash script. Commented Aug 6, 2017 at 17:35
  • My question is does the script know it has to look for 2? If yes, how? Command line argument? Commented Aug 6, 2017 at 17:36
  • No the script doesn't know what to look for. My logic was to write a while() loop to loop through 1 to 1000 to see if any of those values are missing and if it is missing then return the first missing value and exit the loop Commented Aug 6, 2017 at 17:38

4 Answers 4

5

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.

Sign up to request clarification or add additional context in comments.

7 Comments

Interesting. What if the json string is an output of another command, not actually a file
It didn't return anything
@getvivekv it's not supposed to return anything, it's supposed to assign the array to readarray's parameter as randomir showcased in its code sample where the array is named arr.
@getvivekv, I've updated my answer with the example of your exact command. This should give you the array you need.
You are executing readarray in a subshell induced by the pipeline. You need to either use the lastpipe option, or use a process substitution instead of a pipeline (readarray -t arr < <(jq ...)).
|
2

First, Store the Data

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" }

Parsing the Data Into a Bash Array

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.

Caveat

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.

Comments

1

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],

1 Comment

I need the missing value first, and if there is nothing missing in the sequence, the next higher value. I just tried this switching to -s breaks the existing code, I'll re-test it
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.