0
melon@machine: ~/$ cat /tmp/test.json 
[
    "One Entry Here",
    "Two Entry Here",
    "Three Entry Here",
    "Four Entry Here"
]
melon@machine: ~/$ jq -sr '.[]|  @sh' /tmp/test.json
'One Entry Here' 'Two Entry Here' 'Three Entry Here' 'Four Entry Here'
melon@machine: ~/$ BANANA=( 'One Entry Here' 'Two Entry Here' 'Three Entry Here' 'Four Entry Here' )
melon@machine: ~/$ echo ${BANANA[1]}
Two Entry Here

The above shows the expected behavior, and shows me manually setting $BANANA to the output of the jq command.

This behavior changes if the variable uses command substitution instead:

melon@machine: ~/$ BANANA=( $(jq -sjr '.[]|  @sh' /tmp/test.json) )
melon@machine: ~/$ echo ${BANANA[1]}

melon@machine: ~/$ echo ${BANANA[0]}
'One Entry Here' 'Two Entry Here' 'Three Entry Here' 'Four Entry Here'

While it is true that I am trying to convert a JSON array into a BASH array to iterate over, and there are other suggested methods, my question is Why is the behavior different here when using command substitution, and is there anything that I can do to have the command substitution behave like when I manually copy and paste the jq output into the variable myself?


  • GNU bash, version 5.0.3(1)-release (x86_64-pc-linux-gnu)
  • GNU bash, version 5.1.4(1)-release (x86_64-pc-linux-gnu)
melon@machine: ~/$ mapfile -t BANANA < <(jq '.[]' /tmp/test.json)
melon@machine: ~/$ echo ${BANANA[0]}
"One Entry Here"
melon@machine: ~/$ echo ${BANANA[1]}
"Two Entry Here"

mapfile works, but I'm still curious about the difference when using command substitution.

Even after adding the -j flag to jq to suppress ending newline, the results are the same.

7
  • 1
    I'm puzzled by your results - I would expect BANANA[0] to be 'One and BANANA[1] to be Entry i.e. the the (unquoted) command substitution would be split on whitespace. Your results are more consistent with a quoted command substitution (or with an empty or unset IFS) Mar 9, 2021 at 17:20
  • I've switched to focus on how jq may be causing the difference, and it seems that it may be providing a newline at the end of the output, which may be causing a difference. Simply adding -j flag to suppress the newline doesn't seem to help. Thank you for your thoughts.
    – earthmeLon
    Mar 9, 2021 at 17:29
  • If your end goal is to get the JSON elements into a bash array, then I'd suggest something like mapfile -t BANANA < <(jq -r '.[]' /tmp/test.json) Mar 9, 2021 at 17:33
  • Thank you, originally when trying mapfile, I was using the same jq flags and now that I've adjusted those, mapfile does work.
    – earthmeLon
    Mar 9, 2021 at 17:42
  • Get out of the habit of using ALLCAPS variable names, leave those as reserved by the shell. One day you'll write PATH=something and then wonder why your script is broken. Mar 9, 2021 at 18:18

1 Answer 1

2

I would do one of:

IFS=$'\t' read -ra banana < <(jq -sr '.[] | @tsv' test.json)'

which gives the expected:

$ declare -p banana
declare -a banana=([0]="One Entry Here" [1]="Two Entry Here" [2]="Three Entry Here" [3]="Four Entry Here")'

Or use declare which is kind of like eval but just for variable assignments:

declare -a "banana=($(jq -sr '.[] | @sh' test.json))"

This forces the shell to do a 2nd round of expansions so that the quotes emitted by jq will be process properly by the shell. Otherwise you get:

$ banana=($(jq -sr '.[] | @sh' test.json))
$ declare -p banana
declare -a banana=([0]="'One" [1]="Entry" [2]="Here'" [3]="'Two" [4]="Entry" [5]="Here'" [6]="'Three" [7]="Entry" [8]="Here'" [9]="'Four" [10]="Entry" [11]="Here'")

or

$ banana=("$(jq -sr '.[] | @sh' test.json)")
$ declare -p banana
declare -a banana=([0]="'One Entry Here' 'Two Entry Here' 'Three Entry Here' 'Four Entry Here'")
1
  • Thanks. This has a lot of good information and is helpful to get the JSON array into BASH, but I'm still very curious about the difference in behavior as I described.
    – earthmeLon
    Mar 9, 2021 at 22:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .