7

Given this file:

$ cat fruits.json [ { "name": "apple" }, { "name": "banana\nfofanna" }, { "name": "my kiwi" } ] 

how can one use jq to retrieve a list of fruit names which the shell use as data, for example to populate an array?

Something equivalent to the assignment to the fruits array below:

$ fruits=( 'apple' 'banana fofanna' 'my kiwi' ) $ for f in "${fruits[@]}" ;do echo "<$f>"; done <apple> <banana fofanna> <my kiwi> 

None of the following work:

$ fruits=( $(jq -r ' .[].name ' fruits.json)) $ fruits=( $(jq -r ' .[].name | @sh ' fruits.json)) $ fruits=( $(jq ' .[].name | @sh ' fruits.json)) 
2

3 Answers 3

17

The first two attempts at the end of the question do not work because they rely on the shell splitting the output of jq on whitespace. Since newline is one type of whitespace, you lose the newlines (and tabs and the original spaces) in the data.

The attempts additionally fail to quote the string data, so you would get filename globbing happening if any string contained filename globbing characters.

The last attempt fails for similar reasons but additionally does not properly decode the data, leaving encoded newlines in place.


Using the built-in @sh operator in jq to quote the data and build an array assignment:

eval "$( jq -r '"fruits=(" + (map(.name)|@sh) + ")"' fruits.json )" 

For the given JSON data, this would cause the following assignment to be evaluated by the current shell, creating the array fruits:

fruits=('apple' 'banana fofanna' 'my kiwi') 

After evaluating that statement,

$ printf '<%s>\n' "${fruits[@]}" <apple> <banana fofanna> <my kiwi> 

As an alternative, the following would append each name element's value to the shell array:

$ jq -r '"fruits+=(" + (.[].name | @sh) + ")"' fruits.json fruits+=('apple') fruits+=('banana fofanna') fruits+=('my kiwi') 
$ unset -v fruits $ eval "$(jq -r '"fruits+=(" + (.[].name | @sh) + ")"' fruits.json)" 
$ printf '<%s>\n' "${fruits[@]}" <apple> <banana fofanna> <my kiwi> 
3

You did not mention what shell; for ZSH one might use

% fru=( ${(fQg::)"$(jq '.[].name' ~/tmp/fru)"} ) % for f in $fru; printf '<%s>\n' $f <apple> <banana fofanna> <my kiwi> % =echo -n "<${fru[2]}>" | od -bc 0000000 074 142 141 156 141 156 141 012 146 157 146 141 156 156 141 076 < b a n a n a \n f o f a n n a > 0000020 

which performs the jq call, then f splits the result on newlines, and Q removes the quoting that the jq output leaves on the strings, and g:: acts like the builtin echo to perform interpolation on such strings as \n in the input.

1
  • Interesting that this technique in zsh doesn't need to use eval. Commented Aug 2, 2022 at 19:58
0

Based on answers and comments from @Kusalananda, @roaima, and @StéphaneChazelas and in the similar question mentioned in a comment above, I was able to come up with:

list=$( jq -r ' .[].name | @sh ' fruits.json ) || exit 1 eval "fruits=( $list )" || exit 1 printf "<%s>\n" "${fruits[@]}" 

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.