Unix Power ToolsUnix Power ToolsSearch this book

37.5. Quoting and Command-Line Parameters

Q: I need to pass a shell script some arguments with multiple words. I thought that putting quotes (Section 27.12) around command-line arguments would group them. The shell script seems to ignore the quoting, somehow. Here's a simple example:

$ cat script
   ...
for arg in $*
do
    echo "Argument is $arg"
done
$ script '1 2 3' 4
   ...
Argument is 1
Argument is 2
Argument is 3
Argument is 4

A: This is the way $* is defined to work. $* expands to:

$1 $2

[not <">$1<"> <">$2<"> -- JP] if there are two arguments. Hence the for loop reads:

for arg in 1 2 3 4

Note that the quotes are gone. What you wanted the shell to see was:

for arg in '1 2 3' 4

You can't get that, but you can get something that is good enough:

"$@" Section 35.20

for arg in "$@"

In effect, $@ expands to:

$1" "$2

Putting "" s around $@, the effect is:

for arg in "$1" "$2"

Shell quoting is unnecessarily complex. The C shell actually has the right idea (variables can be set to "word lists"; argv is such a list), but its defaults and syntax for suppressing them make for an artless programming language:

foreach arg ($argv:q)      # colon q ?!?

For the special case of iterating a shell variable over the argument list as it stands at the beginning of the iteration, the Bourne shell provides the construct for arg do [i.e., no in list -- JP]:

for arg
do echo "Argument is $arg"
done

The example produces:

Argument is 1 2 3
Argument is 4

"$@" is still needed for passing argument lists to other programs. Unfortunately, since $@ is defined as expanding to:

$1" "$2...$n-1" "$n

(where n is the number of arguments), when there are no arguments, "$@" expands to "", and "" produces a single argument. [Many Unix vendors considered this a bug and changed it so that it produces no arguments. -- JP] The best solution for this is to use, for example:

% cat bin/okeeffe
#! /bin/sh
exec rsh okeeffe.berkeley.edu -l torek ${1+"$@"}
%

The construct ${1+"$@"} means "expand $1, but if $1 is not defined, use "$@" instead." [You don't need this on Bourne shells with the "bug fix" I mentioned, or on bash et al. -- JP] Hence, if there are no arguments, we get $1 (which is nothing and produces no arguments); otherwise, we get "$@" (which expands as above). ${var+instead} is one of several sh "expansion shortcuts" (Section 36.7). Another more generally useful one is ${var-default}, which expands to $var, but if var is not set, to default instead. All of these can be found in the manual for sh, which is worth reading several times, experimenting as you go.

bash has a variety of similar but expanded mechanisms as well, involving a colon before the modifier:

foo=${bar:-baz}             if bar set and non-null, substitute value, else substitute baz...
fum=${fee:=foe}             if fee unset or is null, set it to foe, value then substituted...
fiend=${jeckyll::=hyde}     set jeckyll to hyde, then substitute value... (zsh only)
${required?"error"}        if required set or non-null, substitute its value,
                                  else return "error" and exit...
man=${fullmoon:+wolfman}    if fullmoon set and non-null, substitute wolfman, 
                                                        else substitute nothing...

See the bash manual page's section on parameter expansion. ksh, pdksh, and zsh also have support for the same syntax; zsh has an entire manual page devoted to just parameter expansions: zshexpn(1). Poke around; there's lots of good stuff to explore.

--CT and SJC



Library Navigation Links

Copyright © 2003 O'Reilly & Associates. All rights reserved.