Bash – Can it be explained; the difference in array behavior between the use of array=$( command ) and array=( $( command ) )

arraybashcommand-substitutionsubshell

I understand command substitution. I understand subshells. I do not understand why using a subshell changes the structure of my array.

Given this command output:
(use of openstack command is not intended to be relevant)

bash$ floating ip list -c 'Floating IP Address' -f value
172.25.250.106
172.25.250.107
172.25.250.101

Attempt to capture in an array, but all addresses end up in element 0:

bash$ float=$( openstack floating ip list -c 'Floating IP Address' -f value )
bash$ echo ${float[@]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[@]}
1
bash$ echo ${float[0]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[0]}
44

The whole output was captured as a string and not parsed into elements. I was expecting each word to become an element. When I repeat this to ensure that each IP address is quoted (using -f csv instead of -f value), the results are the same.

Next, I put the command substitution in a subshell:

bash$ unset float
bash$ float=( $( openstack floating ip list -c 'Floating IP Address' -f value ) )
bash$ echo ${float[@]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[@]}
3
echo ${float[0]}
172.25.250.106
echo ${#float[0]}
14

This was the behavior I expected originally. I also notice that building the array using a read statement worked as expected:

bash$ unset float
bash$ read -a float <<< $( openstack floating ip list -c 'Floating IP Address' -f value )
bash$ echo ${float[@]}
172.25.250.106 172.25.250.107 172.25.250.101
bash$ echo ${#float[@]}
3
echo ${float[0]}
172.25.250.106
echo ${#float[0]}
14

I would like to see the original command substitution work. Wondering if I should have set a field separator first or what else I am missing. I am trying to understand what causes the difference in behavior.

Best Answer

float=$( openstack floating ip list -c 'Floating IP Address' -f value )

This creates a string variable, not an array variable. The string is the output of the command, minus any trailing newline.

If you attempt to use a string variable as an array, it's treated as a single-element array with the string value at position 0.

float=( $( openstack floating ip list -c 'Floating IP Address' -f value ) )

This does not “put the command substitution in a subshell”. The command substitution itself $(…) creates a subshell. The parentheses around it do not create another subshell: they create an array. The array content it the list of words resulting from taking the output of the command, removing trailing newlines, splitting into a list of whitespace-separated words, and replacing any element of this list that contains wildcard characters that match one or more files by the list of matching file names.

Parentheses create a subshell when they're at a location where a command is expected. In var=(…), what is expected immediately after the equal sign is not a command, but a value for assignment. In this context, the parentheses indicate that the value is an array.

Related Question