Search code examples
arraysbashshellifs

Surprising array expansion behaviour


I've been surprised with the line marked (!!) in the following example:

log1 () { echo $@; }
log2 () { echo "$@"; }

X=(a b)
IFS='|'

echo ${X[@]}   # prints a b
echo "${X[@]}" # prints a b
echo ${X[*]}   # prints a b
echo "${X[*]}" # prints a|b
echo "---"
log1 ${X[@]}   # prints a b
log1 "${X[@]}" # prints a b
log1 ${X[*]}   # prints a b
log1 "${X[*]}" # prints a b (!!)
echo "---"
log2 ${X[@]}   # prints a b
log2 "${X[@]}" # prints a b
log2 ${X[*]}   # prints a b
log2 "${X[*]}" # prints a|b

Here is my understanding of the behavior:

  • ${X[*]} and ${X[@]} both expand to a b
  • "${X[*]}" expands to "a|b"
  • "${X[@]}" expands to "a" "b"
  • $* and $@ have the same behavior as ${X[*]} and ${X[@]}, except for their content being the parameters of the program or function

This seems to be confirmed by the bash manual.

In the line log1 "${X[*]}", I therefore expect the quoted expression to expand to "a|b", then to be passed to the log1 function. The function has a single string parameter which it displays. Why does something else happen?

It'd be cool if your answers were backed by manual/standard references!


Solution

  • IFS is used not just to join the elements of ${X[*]}, but also to split the unquoted expansion $@. For log1 "${X[*]}", the following happens:

    1. "${X[*]}" expands to a|b as expected, so $1 is set to a|b inside log1.
    2. When $@ (unquoted) is expanded, the resulting string is a|b.
    3. The unquoted expansion undergoes word-splitting with | as the delimiter (due to the global value of IFS), so that echo receives two arguments, a and b.