A variable that holds a variable number of values is called an array.
var=value
Is scalar variable assignment syntax, where one value is assigned to a scalar variable.
var=( one two 'three or 3' )
Is array variable assignment syntax¹, where you can assign any number (even 0²) of values to an array/list variable.
var[n]=foo
Is also scalar assignment syntax but assigns one value to the nth element³ of the array.
So, here you want:
extra_options=( -I ../dir1 -I ../dir2 -I '/opt/my software/include' )
gcc -M $extra_options somefile.c
Note that $extra_options
expansion skips the empty elements of the $extra_options
array if any, like $scalar
expands to no argument rather than one empty element if $scalar
is empty.
You need "$scalar"
or "$array[@]"
or "${(@)array}"
(or "${array[@]}"
, compatible with ksh-like shells such as bash and reminiscent of the "$@"
of the Bourne shell) to preserve the empty elements.
Note that in other Bourne-like shells, in:
TEMPP='-I ../dir1 -I ../dir2'
gcc -M $TEMPP somefile.c
$TEMPP
undergoes an implicit so-called split+glob operator, it is not (thankfully!) as if the value of $TEMPP
had been embedded as-is in the shell code like in gcc -M -I ../dir1 -I ../dir2 somefile.c
.
If it were the case4, with:
TEMPP='foo>/etc/passwd;reboot' # or '$(reboot>/etc/passwd)'
echo $TEMPP
Would overwrite /etc/passwd
and reboot for instance. Shell syntax including ;
, >
operators, ${...}
, $(...)
expansions, quotes is not recognised or handled upon variable expansions. It's however not expanded as-is like in zsh or other saner modern shells (like rc, es, fish, akanga...) or other programming languages, but undergoes that split+glob operator.
The value of $TEMPP
is only split on characters of $IFS
and then subject to globbing (aka filename generation or pathname expansion). What that means is that:
TEMPP='-I ../dir1 -I ../dir2'
gcc -M $TEMPP somefile.c
Only works if $IFS
, at the point of interpreting the gcc...
line happens to contain the space character and doesn't contain any of the other characters in $TEMPP
(thankfully, that's the case with the default value of $IFS
). For instance, if $IFS
contained i
instead, gcc
would be called with -I ../d
, r1 -I ../d
and r2
arguments instead.
And:
TEMPP='-I ../dir1 -I ../dir2 -I "/opt/my software/include"'
gcc -M $TEMPP somefile.c
Wouldn't work, as we just have splitting on $IFS
, not interpretation of shell syntax such as quoting, so if you had to pass arguments containing spaces, you'd need a different separator for the splitting such as:
TEMPP='-I,../dir1,-I,../dir2,-I,/opt/my software/include'
IFS=,
gcc -M $TEMPP somefile.c
For instance. And if you had values containing globbing operators such as *
, ?
, [...]
, you would need to issue a set -o noglob
command before the expansion to make sure globs are not expanded. See also Security implications of forgetting to quote a variable in bash/POSIX shells for the security implications of that misdesign of Bourne-like shells (other than zsh).
In zsh, if you want $IFS
-splitting and/or globbing (but you generally don't as zsh like most other modern shells has arrays), you have to request it explicitly, $=scalar
to split on $IFS
, $~scalar
for the contents of $scalar
to be considered as a pattern and subject to globbing or $=~scalar
to have both and therefore the equivalent of $scalar
in other Bourne-like shells.
See also ${(s[whatever])scalar}
to split on arbitrary separators instead of $IFS
, with ${(f)scalar}
and ${(0)scalar}
as shorthands to split on linef
eed (aka newline) or NULs.
And if you need zsh
to interpret code written for other Bourne-like or Korn-like shells, you'd use its sh
or ksh
emulations where compatibility with those shells is improved with:
emulate ksh
: change the emulation definitely (though emulate zsh
would restore the default saner mode.
emulate -L ksh
: same but only L
ocally (in the current function for instance).
emulate ksh -c 'ksh/bash code here'
or emulate ksh -c 'source some-ksh-or-bash-script'
to interpret only the given code in that emulation mode.
In sh or ksh emulation, IFS-splitting and globbing are done implicitly upon unquoted parameter expansions in list contexts like in sh/ksh/bash (the shwordsplit
and globsubst
and many other options that affect the behaviour of the shell are tweaked to align with the behaviour of those other shells as closely as possible).
¹ inspired by the set var = ( one two 'three or 3' )
of csh, the first shell to introduce arrays and later copied by other shells like ksh93 (though earlier versions of ksh had set -A var one two 'three or 3'
for that), bash or yash.
² note: in ksh93, var=()
without declaring var
as array first creates a compound variable rather than an array variable.
³ in zsh and all other shells but with one exception: ksh (and bash which copied ksh instead of zsh), where instead array indices start at 0 and arrays are sparse, so array[1]=x
would assign the second element if the one with index 0 was also set, and array[123]=x
would assign the first element if all those from 0 to 122 were not otherwise set.
4actually, that was the case in the first (very simple and primitive) Unix shell from the early 70s which didn't have variables nor command substitution but had $1
...$9
positional parameters which expanded like that.