Reputation: 103
$ freebsd-version
10.3-RELEASE-p17
$
portinstall() {
port="$1"; shift
env="$@"
#1
env "$@" printenv | grep -E '^WITH(OUT)?='
#2
env "$env" printenv | grep -E '^WITH(OUT)?='
#3
env "$*" printenv | grep -E '^WITH(OUT)?='
#4
env $@ printenv | grep -E '^WITH(OUT)?='
#5
env $* printenv | grep -E '^WITH(OUT)?='
}
portinstall foo/bar WITH='baz xyzzy' WITHOUT='quux'
Only #1 works, its output:
WITHOUT=quux
WITH=baz xyzzy
With #2,#3 (WITH is just one variable "baz xyzzy WITHOUT=quux"):
WITH=baz xyzzy WITHOUT=quux
With #4,#5
env: xyzzy: No such file or directory
My main question is why #1 works but the seemingly equivalent #2 doesn't?
Upvotes: 3
Views: 62
Reputation: 754570
Using "$@"
is most often correct, but maybe this analysis will help you understand why.
Your code is approximately:
env="$@"
#1
env "$@" printenv | grep -E '^WITH(OUT)?='
#2
env "$env" printenv | grep -E '^WITH(OUT)?='
#3
env "$*" printenv | grep -E '^WITH(OUT)?='
#4
env $@ printenv | grep -E '^WITH(OUT)?='
#5
env $* printenv | grep -E '^WITH(OUT)?='
and you're invoking the script with arguments WITH='baz xyzzy' WITHOUT='quux'
. Yes, you've got a function and a spare argument as $1
adding to the complexity, but they're mostly irrelevant; this is the core.
In general, $*
maps to the words in the arguments separated by spaces; $@
does the same. When enclosed in double quotes, they behave differently: "$@"
expands to the set of arguments preserving internal spaces, while "$*"
maps to a single string with internal spaces preserved and a single space between arguments.
However, in the context of env="$@"
, the assignment behaves like env="$*"
— you end up with a single string in the variable. To preserve the separate arguments, in Bash, you'd use an array:
env=("$@")
and you could print them using:
printf '%s\n' "${env[@]}"
However, that's off at a tangent.
With #1, you end with an invocation of env
with two valid, space-preserving assignments, which printenv
duly prints and grep
filters, giving you the two variable values. This is why "$@"
is usually correct.
With #2 and #3, you end up with an invocation of env
with a single string that starts WITH=
(so it assigns to the variable WITH
, and the remainder of the string is baz xyzzy WITHOUT=quux
, which is why you see that as the output. There's a single environment variable — WITH — but its value contains spaces and an assignment.
With #4 and #5, you've run:
env WITH=baz xyzzy WITHOUT=quux printenv
Since xyzzy
is not an assignment, it is treated as a command to be invoked with arguments WITHOUT=quux
and printenv
(and with WITH=baz
added to the environment), but you don't have a program called xyzzy
on your PATH, so env
gives you the error. You could create a program xyzzy
to show that's what's happening.
With the array env
, you could run:
env "${env[@]}" printenv | …
and you'd get the same result as #1.
Upvotes: 2