bash - array expansion and function calls

154 views Asked by At

SETUP:

GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin21)
Copyright (C) 2007 Free Software Foundation, Inc.

TEST:

check_this() { echo "   FIRST: {$1} SECOND: {$2} THIRD: {$3}"; }

foo=(1 2 3)
expression="${foo[@]}"
echo "via expression:"
check_this "$expression"
echo "directly:"
check_this "${foo[@]}"

OUTPUT:

via expression:
   FIRST: {1 2 3} SECOND: {} THIRD: {}
directly:
   FIRST: {1} SECOND: {2} THIRD: {3}

QUESTION(s):

  • what is happening here?
  • why does "${foo[@]}" not expand to a single string?
  • how can I make the directly case above behave the via variable case?
  • Please, explain what a construct ${foo[*]} and ${foo[@]} actually is and what the quoting operation does to it.
2

There are 2 answers

0
John Bollinger On BEST ANSWER
  • what is happening here?
  • why does "${foo[@]}" not expand to a single string?

Because that's the explicitly defined behavior -- and a very useful one. When an array-valued variable indexed by @ is expanded inside double quotes, the result is a separate word for each element, or nothing if there are no elements.

  • how can I make the directly case above behave the via variable case?

By using "${foo[*]}" instead of "${foo[@]}".

  • Please, explain what a construct ${foo[*]} and ${foo[@]} actually is and what the quoting operation does to it.

Both ${foo[*]} and ${foo[@]} represent a sequence of the elements of array-valued variable foo. They differ only with respect to their behavior when expanded inside double quotes, exactly as you are exploring.

When expanded inside double quotes, ${foo[*]} gives you a single string containing a list of all the elements, separated by the first character in $IFS (usually a space). If there are no elements then the result is equivalent to an empty pair of quotes (""). This is approximately as if you had instead written "${foo[0]} ${foo[1]} ${foo[2]} ...".

When expanded inside double quotes, ${foo[@]} gives you a separate string for each element. If there are no elements then the result is equivalent to nothing at all. This is approximately as if you had instead written "${foo[0]}" "${foo[1]}" "${foo[2]}" .... Of the two, this is usually the one you want in practice.

These differences are analogous to those between $* and $@.

Perhaps it would be a mnemonic aid to associate * with "all" and @ with "each".

0
M. Nejat Aydin On

"${foo[@]}" expands to a list of words, one per array element. For this instance, it is equivalent to a list of three-word:

"${foo[0]}" "${foo[1]}" "${foo[2]}"

In contrast, the expansion "${foo[*]}" yields one word:

"${foo[0]} ${foo[1]} ${foo[2]}"

which is the concatenation of all array elements with the first character of IFS between them (I assume here the value of IFS is $' \t\n', that is its default value).

The simple assignment expression="${foo[@]}" is an interesting case. Since word splitting isn't performed on expansions in simple assignments,

expression="${foo[@]}"
expression=${foo[@]}
expression="${foo[*]}"
expression=${foo[*]}

are all equivalent. As an aside, in order to create a copy of an array, array assignment syntax can be used like this:

bar=("${foo[@]}")

In order to get the same output as that of check_this "$expression", you can replace the @ with * in check_this "${foo[@]}":

check_this "${foo[*]}"