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() {
typeset 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.
./test.sh
runs test.sh
as a separate program. It may happen to be a bash script, if the file test.sh
starts with #!/bin/bash
. But it could be something else altogether.
. ./test.sh
executes the code of the file test.sh
inside the running instance of bash. It works as if the content file test.sh
had been included textually instead of the . ./test.sh
line. (Almost: there are a few details that differ, such as the value of $BASH_LINENO
, and the behavior of the return
builtin.)
source ./test.sh
is identical to . ./test.sh
in bash (in other shells, source
may be slightly different or not exist altogether; .
for inclusion is in the POSIX standard).
The most commonly visible difference between running a separate script with ./test.sh
and including a script with the .
builtin is that if the test.sh
script sets some environment variables, with a separate process, only the environment of the child process is set, whereas with script inclusion, the environment of the sole shell process is set. If you add a line foo=bar
in test.sh
and echo $foo
at the end of the calling script, you'll see the difference:
$ cat test.sh
#!/bin/sh
foo=bar
$ ./test.sh
$ echo $foo
$ . ./test.sh
$ echo $foo
bar
Best Answer
Yes, there is a big difference.
&&
is short-circuiting, so the subsequent command would be executed only if the previous one returned with an exit code of0
.Quoting from the manual:
On the other hand, a script containing
would execute the second expression even if the first failed. (Unless you specified the script to exit on error by saying
set -e
.)EDIT: Regarding your comment whether:
is the same as:
The answer is usually. Bash parses an entire statement block before evaluating any of it. A ; doesn't cause the previous command to be evaluated. If the previous command were to have an effect on how the subsequent one would be parsed, then you'd notice the difference.
Consider a file containing aliases, lets call it
alias
, with an entry:Now consider a script containing:
and another one containing:
then you might think that both would produce the same output.
The answer is NO. The first one would produce
foo
but the second one would report:To clarify further, it's not
expand_aliases
that's causing the problem. The problem is due to the fact that a statement like:would be parsed in one go. The shell doesn't really know what
f
is, this causes the parser to choke.