43

This Bash guide says:

If the index number is @ or *, all members of an array are referenced.

When I do this:

LIST=(1 2 3)
for i in "${LIST[@]}"; do
  echo "example.$i"
done

it gives the desired result:

example.1
example.2
example.3

But when I use ${LIST[*]}, I get

example.1 2 3

instead.

Why?

Edit: when using printf, @ and * actually do give the same results.

conny
  • 113
arjan
  • 717

3 Answers3

43

The difference is subtle; "${LIST[*]}" (like "$*") creates one argument, while "${LIST[@]}" (like "$@") will expand each item into separate arguments, so:

LIST=(1 2 3)
for i in "${LIST[@]}"; do
    echo "example.$i"
done

will deal with the list (print it) as multiple variables.

But:

LIST=(1 2 3)
for i in "${LIST[*]}"; do
    echo "example.$i"
done

will deal with the list as one variable.

Nidal
  • 8,956
  • Do you know where the difference between echo and printf comes from? Because with printf in the for loop, the * list reference is treated as multiple variables. – arjan Jun 07 '14 at 14:45
  • What does it mean for something to be dealt with as one versus multiple variables? I wonder if you could provide a practical example to illustrate the difference. – fraxture Mar 22 '19 at 13:47
  • Wow, it's worth while mentioning that when you do the one with the star that it separates the values by the value of "IFS". For example, try this: IFS="/" && LIST=(1 2 3); for i in "${LIST[*]}"; do echo "$i"; done; and you should get "1/2/3". Hell, if you couldn't remember any other method you could use this replace a character now that I think about it by setting IFS to the before character, splitting it using read -a <<< '$string", and then changing the IFS to the replacement before looping through and reconstructing it. Rather brute force but still... – Brent Rittenhouse Sep 15 '20 at 09:35
  • Hey, check it out! https://i.imgur.com/WYYtdvQ.png. It totally worked. Script is: IFS=" " && read -a arrBefore; IFS="/" && for i in "${arrBefore[*]}"; do echo "$i"; done; – Brent Rittenhouse Sep 15 '20 at 09:54
  • 1
    Yes and note that it is the surrounding double quotes that in turn preserves the "${LIST[*]}" as "1 2 3" and prevents it from getting split one more time into "1" "2" "3". Always double quote variable expansions UNLESS YOU WANT a spaces in their expanded value to cause splitting. – conny Mar 25 '21 at 09:46
  • https://stackoverflow.com/a/3348659/19166437 – envs_h_gang_5 Mar 16 '23 at 11:00
10

Using [*] will create a single string, with each element of your array joined with the first character of $IFS (a space by default) in between elements¹.

Using [@] will create a list.

Examples:

Creating an array:

$ list=( a b "big fish" c d )

Printing each element individually:

$ printf 'data: ---%s---\n' "${list[@]}"
data: ---a---
data: ---b---
data: ---big fish---
data: ---c---
data: ---d---

Creating a single string and printing that:

$ printf 'data: ---%s---\n' "${list[*]}"
data: ---a b big fish c d---

Again, but with a custom delimiter:

$ IFS='/'
$ printf 'data: ---%s---\n' "${list[*]}"
data: ---a/b/big fish/c/d---

Note that using these expansions without double quotes rarely makes sense.


¹ or nothing if $IFS is set but empty (after IFS=) or a space if $IFS is unset (after unset -v IFS) with some variations between shells if the first character of $IFS is a multibyte character, or something that cannot be decoded as a character.

Kusalananda
  • 333,661
3

Consider the simple case that list elements do not contain special characters (e.g. blanks). Then the quotes can be dropped. Then @ and * will give the same result.

LIST=(1 2 3)
for i in ${LIST[*]}; do
  echo "example.$i"
done

ouput:

example.1
example.2
example.3

Now consider the case that elements contain blanks:

LIST=(1 "a b" 3)
for i in ${LIST[*]}; do
  echo "example.$i"
done

In this case, @ and * still give the same result:

example.1
example.a
example.b
example.3

But it is not what we desire since "a b" is split as two elements rather than one elmement. So we need to use quotes to prevent this:

LIST=(1 "a b" 3)
for i in "${LIST[*]}"; do
  echo "example.$i"
done

But the result is example.1 a b 3. Next we change * to @:

LIST=(1 "a b" 3)
for i in "${LIST[@]}"; do
  echo "example.$i"
done

We finally get the desired result:

example.1
example.a b
example.3

Summarizing the above result, we see that @ allows element-wisely operation when being operated by some operators, e.g. quote marks.

Youjun Hu
  • 141