13

I'm trying to populate an associative array with the output of a command. I can do it without a command as:

$ declare -A x=( [first]=foo [second]=bar )
$ echo "${x[first]}, ${x[second]}"
foo, bar

and I can populate a non-associative array with command output as:

$ declare y=( $(echo 'foo bar') )
$ echo "${y[0]}, ${y[1]}"
foo, bar

but when I try to build on both of the above to create a statement that will populate an associative array from a command, I get the following error message:

$ declare -A z=( $(echo '[first]=foo [second]=bar') )
-bash: z: $(echo '[first]=foo [second]=bar'): must use subscript when assigning associative array

Why am I getting that error message and what is the correct syntax to populate an associative array with the output of a command? I am trying to avoid using eval for the usual reasons, do not want to use a temp file, and of course echo is just being used as an example of a command that produces the effect in question, the real command will be more complicated.

So, based on a couple of the answers below, it looks like it was just my quoting that was a problem:

$ declare -A z="( $(echo '[first]=foo [second]=bar') )"
$ echo "${z[first]}, ${z[second]}"
foo, bar

and with spaces in the indices and values:

$ declare -A z="( $(echo '[first field]="foo with space" [second]="space bar"') )"
$ echo "${z[first field]}, ${z[second]}"
foo with space, space bar

EDIT in response to a question in the comments about why the quotes are necessary (How do I populate a bash associative array with command output?) - I don't exactly know but maybe someone else can explain using the results of this script as reference (not expecting the specified indices to be used in the indexed arrays, they're just part of the strings being populated as the array values):

$ cat tst.sh
#!/bin/env bash

set -x

printf 'Indexed, no quotes\n'
declare -a w=( $(echo '[first]=foo [second]=bar') )
declare -p w

printf '\n---\n'

printf 'Indexed, with quotes\n'
declare -a x="( $(echo '[first]=foo [second]=bar') )"
declare -p x

printf '\n---\n'

printf 'Associative, no quotes\n'
declare -A y="( $(echo '[first]=foo [second]=bar') )"
declare -p y

printf '\n---\n'

printf 'Associative, with quotes\n'
declare -A z=( $(echo '[first]=foo [second]=bar') )
declare -p z

.

$ ./tst.sh
+ printf 'Indexed, no quotes\n'
Indexed, no quotes
+ w=($(echo '[first]=foo [second]=bar'))
++ echo '[first]=foo [second]=bar'
+ declare -a w
+ declare -p w
declare -a w=([0]="[first]=foo" [1]="[second]=bar")
+ printf '\n---\n'

---
+ printf 'Indexed, with quotes\n'
Indexed, with quotes
++ echo '[first]=foo [second]=bar'
+ declare -a 'x=( [first]=foo [second]=bar )'
+ declare -p x
declare -a x=([0]="bar")
+ printf '\n---\n'

---
+ printf 'Associative, no quotes\n'
Associative, no quotes
++ echo '[first]=foo [second]=bar'
+ declare -A 'y=( [first]=foo [second]=bar )'
+ declare -p y
declare -A y=([second]="bar" [first]="foo" )
+ printf '\n---\n'

---
+ printf 'Associative, with quotes\n'
Associative, with quotes
+ z=($(echo '[first]=foo [second]=bar'))
./tst.sh: line 24: z: $(echo '[first]=foo [second]=bar'): must use subscript when assigning associative array
+ declare -A z
+ declare -p z
declare -A z=()
0

3 Answers 3

13

Here is a traditional while loop approach to populate an associative array from a command's output:

while IFS= read -r; do
   declare -A z+="( $REPLY )"
done < <(printf '[first]=foo [second]=bar\n[third]=baz\n')

# check output
$> echo "${z[first]}, ${z[second]}, ${z[third]}"
foo, bar, baz

# or declare -p
$> declare -p z
declare -A z='([third]="baz" [second]="bar" [first]="foo" )'

EDIT: Your original attempt will also work with proper quotes:

$> unset z

$> declare -A z="( $(echo '[first]=foo [second]=bar') )"

$> declare -p z
declare -A z='([second]="bar" [first]="foo" )'
Sign up to request clarification or add additional context in comments.

8 Comments

That could work. We'd need to add a IFS= to it and we can't use $'...' around the whole thing or it'd convert \t to a tab, etc. but we could probably get literal newlines or even NUL characters (and read with NUL flag) between each array element. Gotta think about that too, thanks!
I was once told on SO that IFS= is not needed if we are using internal $REPLY variable but there is no harm in using it I guess. (edited)
Hmm, the newlines aren't actually making a difference in separating the values,. the script works when the values are just space-separated, the printf just needs one newline at the end.
Ah now I got it. You just needed quote so declare -A z="( $(echo '[first]=foo [second]=bar') )" would work fine but declare -A z=( $(echo '[first]=foo [second]=bar') ) will give error.
Yeah, I just posted that at the end of my question. Can't believe I got bit by not quoting something! Now need to expand it to get the syntax right for handling blank chars in the index and/or value. I'll accept this answer later today unless someone comes along with a good reason not and and a better alternative. Thanks.
|
3

I imagine this is somewhat brittle, but you can make the entire z=(...) assignment the result of a command substitution.

declare -A "$(echo z="($(echo '[first]=foo [second]=bar'))")"

5 Comments

Interesting. I hadn't thought of that. Probably don't want to use echo z=... but some variation of printf 'z="%s" ....' might be robust. Gotta think about that! Thanks.
The correct ending should be )")" not "))", but that's not enough for an edit :)
I guess since there is no unquoted ( for the unquoted ) to pair up with, it doesn't need to be quoted. Fixed anyway, because I'm sure that some other input could probably tip it from working to not-working.
Looks like all I needed was to quote my original assignment, see the bottom of stackoverflow.com/a/39418782/1745001. A tad embarrassing... Thanks again.
Huh. Did I really not try that before the nested monstrosity suggested here?
1

Given that this works:

declare -A z=([first]=$(echo 'foo') [second]=$(echo 'bar'))

I'm guessing that Bash needs to see the associative array initialization list before doing any substitutions. So I don't see a way to avoid eval:

eval "declare -A z=($(echo '[first]=foo [second]=bar'))"

What is a "usual reason" to avoid eval?

2 Comments

Google shell eval evil for many discussions on the issues around eval. Thanks for the response, still hoping there's an alternative.
Looks like all I needed was to quote my original assignment, see the bottom of stackoverflow.com/a/39418782/1745001. A tad embarrassing... Thanks again.

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.