Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

Consider the following code:

foo () {
    echo $*
}

bar () {
    echo $@
}

foo 1 2 3 4
bar 1 2 3 4

It outputs:

1 2 3 4

1 2 3 4

I am using Ksh88, but I am interested in other common shells as well. If you happen to know any particularity for specific shells, please do mention them.

I found the follwing in the Ksh man page on Solaris:

The meaning of $* and $@ is identical when not quoted or when used as a parameter assignment value or as a file name. However, when used as a command argument, $* is equivalent to ``$1d$2d...'', where d is the first character of the IFS variable, whereas $@ is equivalent to $1 $2 ....

I tried modifying the IFS variable, but it doesn't modify the output. Maybe I'm doing something wrong?

share|improve this question
1  
What did you try setting IFS to? How? (If you do it correctly, the output will change.) –  Mikel Jun 25 '12 at 15:18
    
Ah, you made an edit to the question, and it doesn't make sense now. I reverted to your previous version. –  Mikel Jun 25 '12 at 17:03
    
I had edited my question before I got any replies or comments, because it was an honest typo (I copy/pasted the wrong version of the file here). But in any case, if you feel the question makes more sense that way, by all means do revert :) –  rahmu Jun 25 '12 at 17:38
    
You said modifying IFS didn't work. I'm guessing that's because you were using echo $* rather than echo "$*". Either way, please do update your question reflect exactly what commands you tried. –  Mikel Jun 25 '12 at 20:50
    
Indeed you guessed right. It was my bad, I got confused by all these quotes ... –  rahmu Jun 26 '12 at 15:26
add comment

5 Answers

up vote 9 down vote accepted

When they are not quoted, $* and $@ are the same. You shouldn't use either of these, because they can break unexpectedly as soon as you have arguments containing spaces or wildcards.


"$*" expands to a single word "$1c$2c...". Usually c is a space, but it's actually the first character of IFS, so it can be anything you choose.

The only good use I've ever found for it is:

join arguments with comma (simple version)

join1() {
    IFS=,
    echo "$*"
}

join1 a b c   # => a,b,c

join arguments with the specified delimiter (better version)

join2() {
    typeset IFS=$1   # typeset makes a local variable in ksh (see footnote)
    shift
    echo "$*"
}

join2 + a b c   # => a+b+c

"$@" expands to separate words: "$1" "$2" ...

This is almost always what you want. It expands each positional parameter to a separate word, which makes it perfect for taking command line or function arguments in and then passing them on to another command or function. And because it expands using double quotes, it means things don't break if, say, "$1" contains a space or an asterisk (*).


Let's write a script called svim that runs vim with sudo. We'll do three versions to illustrate the difference.

svim1

#!/bin/sh
sudo vim $*

svim2

#!/bin/sh
sudo vim "$*"

svim3

#!/bin/sh
sudo vim "$@"

All of them will be fine for simple cases, e.g. a single file name that doesn't contain spaces:

svim1 foo.txt             # == sudo vim foo.txt
svim2 foo.txt             # == sudo vim "foo.txt"
svim2 foo.txt             # == sudo vim "foo.txt"

But only $* and "$@" work properly if you have multiple arguments.

svim1 foo.txt bar.txt     # == sudo vim foo.txt bar.txt
svim2 foo.txt bar.txt     # == sudo vim "foo.txt bar.txt"   # one file name!
svim3 foo.txt bar.txt     # == sudo vim "foo.txt" "bar.txt"

And only "$*" and "$@" work properly if you have arguments containing spaces.

svim1 "shopping list.txt" # == sudo vim shopping list.txt   # two file names!
svim2 "shopping list.txt" # == sudo vim "shopping list.txt"
svim3 "shopping list.txt" # == sudo vim "shopping list.txt"

So only "$@" will work properly all the time.


typeset is how to make a local variable in ksh (bash and ash use local instead). It means IFS will be restored to its previous value when the function returns. This is important, because the commands you run afterward might not work properly if IFS is set to something non-standard.

share|improve this answer
    
Wonderful explanation, thank you very much. –  rahmu Jun 26 '12 at 15:29
add comment

The code you provided will give the same result. To understand it better, try this:

foo () {
    for i in "$*"; do
        echo "$i"
    done
}

bar () {
    for i in "$@"; do
        echo "$i"
    done
}

The output should now be different. Here's what I get:

$ foo() 1 2 3 4
1 2 3 4
$ bar() 1 2 3 4
1
2
3
4

This worked for me on bash. As far as I know, ksh should not differ much. Essentially, quoting $* will treat everything as one word, and quoting $@ will treat the list as separate words, as can be seen in the example above.

As an example of using the IFS variable with $*, consider this

fooifs () {
    IFS="c"            
    for i in "$*"; do
        echo "$i"
    done
    unset IFS          # reset to the original value
}

I get this as a result:

$ fooifs 1 2 3 4
1c2c3c4

Also, I've just confirmed it works the same in ksh. Both bash and ksh tested here were under OSX but I can't see how that would matter much.

share|improve this answer
    
"changed within a function - doesn't affect the global". Did you test that? –  Mikel Jun 25 '12 at 15:11
    
Yes, I've checked that. For the peace of mind, I'll add the unset IFS at the end to reset it to the original, but it worked for me no problem and doing an echo $IFS resulted in the standard output I get from it. Setting the IFS withing the curly brackets introduces a new scope, so unless you export it, it will not affect the outside IFS. –  Wojtek Rzepala Jun 25 '12 at 16:00
    
echo $IFS doesn't prove anything, because the shell sees the ,, but then does word splitting using IFS! Try echo "$IFS". –  Mikel Jun 25 '12 at 16:43
    
good point. unsetting IFS should solve that. –  Wojtek Rzepala Jun 25 '12 at 17:39
    
Unless IFS had a different custom value before calling the function. But yes, most of the time unsetting IFS will work. –  Mikel Jun 25 '12 at 20:51
add comment

The difference is important when writing scripts that should use the positional parameters in the right way...

Imagine the following call:

$ myuseradd -m -c "Carlos Campderrós" ccampderros

Here there are just 4 parameters:

$1 => -m
$2 => -c
$3 => Carlos Campderrós
$4 => ccampderros

In my case, myuseradd is just a wrapper for useradd that accepts the same parameters, but adds a quota for the user:

#!/bin/bash -e

useradd "$@"
setquota -u "${!#}" 10000 11000 1000 1100

Notice the call to useradd "$@", with $@ quoted. This will respect the parameters and send them as-they-are to useradd. If you were to unquote $@ (or to use $* also unquoted), useradd would see 5 parameters, as the 3rd parameter which contained a space would be split in two:

$1 => -m
$2 => -c
$3 => Carlos
$4 => Campderrós
$5 => ccampderros

(and conversely, if you were to use "$*", useradd would only see one parameter: -m -c Carlos Campderrós ccampderros)

So, in short, if you need to work with parameters respecting multi-word parameters, use "$@".

share|improve this answer
add comment
   *      Expands  to  the positional parameters, starting from one.  When
          the expansion occurs within double quotes, it expands to a  sin‐
          gle word with the value of each parameter separated by the first
          character of the IFS special variable.  That is, "$*" is equiva‐
          lent to "$1c$2c...", where c is the first character of the value
          of the IFS variable.  If IFS is unset, the parameters are  sepa‐
          rated  by  spaces.   If  IFS  is null, the parameters are joined
          without intervening separators.
   @      Expands to the positional parameters, starting from  one.   When
          the  expansion  occurs  within  double  quotes,  each  parameter
          expands to a separate word.  That is, "$@" is equivalent to "$1"
          "$2"  ...   If the double-quoted expansion occurs within a word,
          the expansion of the first parameter is joined with  the  begin‐
          ning  part  of  the original word, and the expansion of the last
          parameter is joined with the last part  of  the  original  word.
          When  there  are no positional parameters, "$@" and $@ expand to
          nothing (i.e., they are removed).

// man bash . is ksh, afair, similar behaviour.

share|improve this answer
add comment

Talking about differences between zsh and bash:

With quotes around $@ and $*, zsh and bash behave the same, and I guess the result is quite standard among all shells:

 $ f () { for i in "$@"; do echo +"$i"+; done; }; f 'a a' 'b' ''
 +a a+
 +b+
 ++
 $ f () { for i in "$*"; do echo +"$i"+; done; }; f 'a a' 'b' ''
 +a a b +

Without quotes, results are the same for $* and $@, but different in bash and in zsh. In this case zsh shows some odd behaviour:

bash$ f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''
+a+
+a+
+b+
zsh% f () { for i in $*; do echo +"$i"+; done; }; f 'a a' 'b' ''  
+a a+
+b+

(Zsh usually don't split textual data using IFS, unless explicitly requested, but notice that here the empty argument is unexpectedly missing in the list.)

share|improve this answer
1  
In zsh, $@ isn't special in this respect: $x expands to at most one word, but empty variables expand to nothing (not an empty word). Try print -l a $foo b with foo empty or undefined. –  Gilles Jun 25 '12 at 23:27
    
@Gilles: Yes, but that's precisely what I find odd :) –  Stéphane Gimenez Jun 25 '12 at 23:33
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.