22

I parsed a json file with jq like this :

# cat test.json | jq '.logs' | jq '.[]' | jq '._id' | jq -s

It returns an array like this : [34,235,436,546,.....]

Using bash script i described an array :

# declare -a msgIds = ...

This array uses () instead of [] so when I pass the array given above to this array it won't work.

([324,32,45..]) this causes problem. If i remove the jq -s, an array forms with only 1 member in it.

Is there a way to solve this issue?

2
  • 4
    Side comment — Why use jq '.logs' | jq '.[]' | jq '._id' when you can use jq '.logs[]._id ? Commented Jan 8, 2019 at 8:08
  • 1
    i am new to bash script and also jq, any suggestions will be welcomed Commented Jan 8, 2019 at 8:12

5 Answers 5

33

We can solve this problem by two ways. They are:

Input string:

// test.json
{
    "keys": ["key1","key2","key3"]
}

Approach 1:

1) Use jq -r (output raw strings, not JSON texts) .

KEYS=$(jq -r '.keys' test.json)
echo $KEYS
# Output: [ "key1", "key2", "key3" ]

2) Use @sh (Converts input string to a series of space-separated strings). It removes square brackets[], comma(,) from the string.

KEYS=$(<test.json jq -r '.keys | @sh')
echo $KEYS
# Output: 'key1' 'key2' 'key3'

3) Using tr to remove single quotes from the string output. To delete specific characters use the -d option in tr.

KEYS=$((<test.json jq -r '.keys | @sh')| tr -d \') 
echo $KEYS
# Output: key1 key2 key3

4) We can convert the comma-separated string to the array by placing our string output in a round bracket(). It also called compound Assignment, where we declare the array with a bunch of values.

ARRAYNAME=(value1 value2  .... valueN)
#!/bin/bash
KEYS=($((<test.json jq -r '.keys | @sh') | tr -d \'\"))

echo "Array size: " ${#KEYS[@]}
echo "Array elements: "${KEYS[@]}

# Output: 
# Array size:  3
# Array elements: key1 key2 key3

Approach 2:

1) Use jq -r to get the string output, then use tr to delete characters like square brackets, double quotes and comma.

#!/bin/bash
KEYS=$(jq -r '.keys' test.json  | tr -d '[],"')
echo $KEYS

# Output: key1 key2 key3

2) Then we can convert the comma-separated string to the array by placing our string output in a round bracket().

#!/bin/bash
KEYS=($(jq -r '.keys' test.json  | tr -d '[]," '))

echo "Array size: " ${#KEYS[@]}
echo "Array elements: "${KEYS[@]}

# Output:
# Array size:  3
# Array elements: key1 key2 key3
Sign up to request clarification or add additional context in comments.

3 Comments

Got @sh command not found for Approach 1, but Approach 2 works perfect! Thanks!
@yfpb, @sh is a jq construct, not a shell command; you need to make sure it's part of the jq query.
Note that these approaches only work correctly if the strings being extracted do not have globbing, quoting, or IFS characters. If they do, you need to actually interpret the quotes produced by jq's @sh filter, not just remove them. Bash can do it for you, see below.
16

To correctly parse values that may have newlines (and any other arbitrary (non-NUL) characters) use jq's @sh filter to generate space-separated quoted strings, and Bash's declare -a to parse the quoted strings as array elements. (No pre-processing required)

foo.json:

{"data": ["$0", " \t\n", "*", "\"", ""]}
str=$(jq -r '.data | @sh' foo.json)
declare -a arr="($str)"   # must be quoted like this
declare -p arr
# declare -a arr=([0]="\$0" [1]=$' \t\n' [2]="*" [3]="\"" [4]="")

Update: jq 1.7 (2023-09)

As of version 1.7, jq has a --raw-output0 option, enabling it to output null-terminated strings which can be read into an array as usual:

mapfile -d '' arr < <(jq --raw-output0 '.data[]' foo.json)
wait "$!"  # use in bash-4.4+ to get exit status of the process substitution

Note on NUL characters in JSON strings

JSON strings may contain NUL characters while shell variables cannot. If your JSON input may contain NUL's, you may need to add some special handling.

  • When using the @sh filter, NUL characters from JSON strings will be silently replaced with the sequence \0. Note that this makes the JSON strings "\\0" and "\u0000" indistinguishable.

  • When using the --raw-output0 option, NUL characters will trigger an error and jq will terminate with an exit status of 5.

Reading multiple/nested arrays

The @sh filter can be combined with --raw-output0 to reliably read multiple arrays at once (or a single nested array) as it will produce a NUL-separated list of space-separated quoted strings.

json='[[1,2],[3,4]]' i=0
while read -r -d ''; do
    declare -a "arr$((i++))=($REPLY)"
done < <(jq --raw-output0 '.[]|@sh' <<<$json)
for ((n=0; n<i; n++)); { declare -p "arr$n"; }
# declare -a arr0=([0]="1" [1]="2")
# declare -a arr1=([0]="3" [1]="4")

1 Comment

This is the most correct answer, since it is going to result in proper error handling even while using set -e.
9

Use jq -r to output a string "raw", without JSON formatting, and use the @sh formatter to format your results as a string for shell consumption. Per the jq docs:

@sh:

The input is escaped suitable for use in a command-line for a POSIX shell. If the input is an array, the output will be a series of space-separated strings.

So can do e.g.

msgids=($(<test.json jq -r '.logs[]._id | @sh'))

and get the result you want.

3 Comments

thanks for the reply, i want to get a result like this : (234, 23, 56), is this possible with @sh formatter? The output is not comma seperated so far
@İlkerDemirci why? Comma separated isn't the correct syntax to read it into a bash array, space separated is.
You are absolutely right, that worked perfectly. Thanks
7

From the jq FAQ (https://github.com/stedolan/jq/wiki/FAQ):

𝑸: How can a stream of JSON texts produced by jq be converted into a bash array of corresponding values?

A: One option would be to use mapfile (aka readarray), for example:

mapfile -t array <<< $(jq -c '.[]' input.json)

An alternative that might be indicative of what to do in other shells is to use read -r within a while loop. The following bash script populates an array, x, with JSON texts. The key points are the use of the -c option, and the use of the bash idiom while read -r value; do ... done < <(jq .......):

#!/bin/bash
x=()
while read -r value
do
  x+=("$value")
done < <(jq -c '.[]' input.json)

Comments

3

++ To resolve this, we can use a very simple approach:

++ Since I am not aware of you input file, I am creating a file input.json with the following contents:

input.json:

{
    "keys": ["key1","key2","key3"]
}

++ Use jq to get the value from the above file input.json:

Command: cat input.json | jq -r '.keys | @sh'

Output: 'key1' 'key2' 'key3'

Explanation: | @sh removes [ and "

++ To remove ' ' as well we use tr

command: cat input.json | jq -r '.keys | @sh' | tr -d \'

Explanation: use tr delete -d to remove '

++ To store this in a bash array we use () with `` and print it:

command:

KEYS=(`cat input.json | jq -r '.keys | @sh' | tr -d \'`)

To print all the entries of the array: echo "${KEYS[*]}"

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.