1

I set a variable TEMPP='-I ../dir1 -I ../dir2'

then I run

gcc -M $TEMPP somefile.c

It seems not to include ../dir1 and ../dir2 in the search list of the include file, and if there is a space at the beginning of the variable, like

TEMPP=' -I ../dir1 -I ../dir2'

it reports an error:

gcc: -I ../common1 -I ../encrypt: No such file or directory

so it seems the variable was treated as a file.

Maybe it will let me separate the directory to avoid this promblem, but those included dirs are generated by another command, and the amount is not constant.

So how can I let a variable in a command be treated as literal seem like a manual input in command line, not an integral string or a file name?

OK...I find this situation only happened in the zsh, but in bash it works well... may be in zsh the expansion of variable is special.

So if anyone can tell me how can this work well in zsh I will appreciate, or I can only do this in bash.

wind
  • 23
  • Your example, as written, should work fine. Please provide the actual command you are running. – Mikel Oct 24 '11 at 16:30

3 Answers3

2

I believe you're trying to control whether white space in "$TEMPP" is interpreted or not. (Note, this is not a GCC problem, it is your shell that is parsing the command line.)

Here is what bash does:

$ F="foo bar baz"
$ for f in $F; do echo $f; done
foo
bar
baz

And here is what zsh does:

$ F="foo bar baz"
$ for f in $F; do echo $f; done
foo bar baz

You need to get zsh to interpret the whitespace. There may be a graceful way to do this in zsh (I don't know, but if you re-title and tag your question, you might get more useful answers). This should work around the problem, but is a bit of a heavy hammer for this problem:

eval gcc -M $TEMPP somefile.c
P.T.
  • 1,635
  • Yes, you are right, at first I don't know the problem is because of zsh, so the title is this, and then I find this work well in bash and I make a supplements in the end, but I did not consider about the title.And thanks for you solution about eval, it works. – wind Oct 24 '11 at 08:48
1

In zsh, unlike other Bourne-style shells, the results of a variable substitution are not split into words that are interpreted as wildcard patterns. So in zsh, if you write

a='hello *.txt'
echo $a

then you see hello *.txt, unlike other shells where you'll see something like hello bar.txt foo.txt.

You can turn on word splitting for one expansion with $=TEMPP. You can turn on word splitting for all expansions with setopt sh_word_split or emulate sh.

You should stick these commands in a makefile and let a real Bourne-style shell (whatever is installed as /bin/sh) run them. Keep zsh for interactive use and your own scripts.

  • Thanks, your answer is solve my zsh problem, and yes, in makefile there is no that problem, in the beginning I use makefile to test these command, it works well, but for some reason I need to run these command in command line, then I run it in zsh and found it works differ from makefile. Thanks for your answer and advice. – wind Oct 25 '11 at 02:21
0

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 linefeed (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 Locally (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.