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.

5
  • It seems to work for me. I tried both @ and * and it seems to produce the same result both the times. What shell you are using? Run echo $SHELL and paste the output to your question.
    – Ramesh
    Jun 7, 2014 at 14:26
  • 1
    My example was wrong, this actually happens only with echo, not with printf, I just noticed.
    – arjan
    Jun 7, 2014 at 14:36
  • 1
    possible duplicate of What is the difference between $* and $@?
    – goldilocks
    Jun 7, 2014 at 17:13
  • 1
    @goldilocks The other question is about $* and $@. Though, the answer would be similar and one question could be considered a subset of the other, they are different questions. Jun 7, 2014 at 19:31
  • 1
    printf and echo give the same result (tested on GNU bash, version 5.0.17), i.e., the difference between @ and * has nothing to do with the difference between printf and echo.
    – Youjun Hu
    Aug 13, 2023 at 2:47

3 Answers 3

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.

6
  • 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 7, 2014 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, 2019 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... Sep 15, 2020 at 9:35
  • Hey, check it out! i.imgur.com/WYYtdvQ.png. It totally worked. Script is: IFS=" " && read -a arrBefore; IFS="/" && for i in "${arrBefore[*]}"; do echo "$i"; done; Sep 15, 2020 at 9: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, 2021 at 9:46
9

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.

0
2

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.

2
  • "the simple case that list elements do not contain blanks" that is not sufficient for dropping quotes safely.
    – muru
    Aug 13, 2023 at 3:38
  • edited to " do not contain special characters (e.g., blanks)".
    – Youjun Hu
    Aug 13, 2023 at 3:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .