Main index

Introducing UNIX and Linux


Introduction to shells

Overview
Why do we need a shell?
Shell syntax
      Types of shell command
      Simple commands
      Pipelines
      Grouping commands
      Exit status
      List commands
Arithmetic
      Operators and functions
Making decisions
      The 'test' statement
            Operators used by 'test'
      The 'if' statement
Loops
      'For' loops
      'While' and 'until' loops
Searching for files
      Arguments to 'find'
Formatted output
      Arguments to 'printf'
Passing information to scripts
      Scripts with arguments
      Parameter expansion
Summary
Exercises

Scripts with arguments

Just as a UNIX command can take arguments, so can a script. After all, a script is a command written by a user. The first argument of a script is referred to within the script as $1, the second by $2, and so on. These are known as positional parameters. They can be manipulated like any other variables, except that they cannot be reset using =. Create a file (argfile, say) containing one line:

echo $1 $2

Now run that script, but give it two arguments:

sh argfile hello 99
hello 99

There are some other 'variable names' that have special meanings when used within a script. The name of the script (i.e. the name of the file containing the script) is denoted by $0, and the number of positional parameters by $#. Note that in this context # does not introduce a comment. Suppose the following script, called showargs, is invoked:

This script is $0, and it has $# arguments
First argument is $1

The output we would get would be:

sh ./showargs foo bar
This script is ./showargs, and it has 2 arguments
First argument is foo
sh showargs "foo bar"
This script is showargs, and it has 1 arguments
First argument is foo bar

Note that $0 uses the name of the script that has been called. In the second invocation, the first argument of showargs is the string "foo bar", not foo - the quotes around it cause it to be considered as a single word.

When a script is given many arguments, accessing them one-by-one using positional parameters is often awkward. We can use $* to refer to them all at once. In other words, the value of $* is the single string "$1 $2 $3 ...". In order to experiment with these parameters, create a script containing

for i in $*
do
echo $i
done

and call it testfile. When it is run, the $* will be replaced by the arguments of the script; thus calling testfile with arguments jo, sam and george, so

testfile jo sam george

would be equivalent to running a script containing:

for i in jo sam george
do
echo $i
done

We must be careful, though; the shell will strip out quotes before passing arguments to a command, and we need to be able to handle

sh testfile jo "Sue Smith" sam

in a sensible manner. To this end we can use $@, which is similar to $*. Edit testfile to replace $* by $@. In both cases the result is the same, namely

sh testfile jo "Sue Smith" sam
jo
Sue
Smith
sam

indicating that the quotes have been stripped before the arguments have been passed to testfile. If, instead, the first line of the script is

for i in "$*"

the quotes are stripped from the arguments, which are then enclosed by a new pair of quotes. Thus the string jo Sue Smith sam is the expansion of $*, which is then quoted within the script indicating a single string, and the output is:

sh testfile jo "Sue Smith" sam
jo Sue Smith sam

If, however, "$@" is used, the arguments to the script are passed without modification, including quotes, to replace "$@", and the quotes are then interpreted within the script:

sh testfile jo "Sue Smith" sam
jo
Sue Smith
sam

If a script requires an indeterminate number of arguments, you may wish to discard the earlier ones - for instance, if they are options and you have finished processing all the options. The command shift will remove $1 from the positional parameters, $2 will become $1 (etc.), and $*, $@ and $# will all be changed accordingly.

Worked example

Write a script called mypager to take arguments that are files and page each of them in turn using more. Additionally, mypager may take a single argument, -i, which will cause a message to be displayed on the screen before each file is paged, giving the name of the file, and requiring the user to press Return to continue.
Solution:

IFLAG=no
if [ "$#" -gt 0 ]          # Make sure there are some files
then    if [ "$1" = "-i" ] # Check if the option is called
        then IFLAG=yes     # If so, reset the flag ...
             shift         # and delete the argument
        fi
fi

for i in "$@"              # Go through each file in turn
do
   if   [ "$IFLAG" = "yes" ]   # If "-i" ...
   then echo "Paging $i"       # output message ...
        echo "Press Return to continue"
        read j                 # wait for Return
   fi
   more "$i"               # Page the file
done

Copyright © 2002 Mike Joy, Stephen Jarvis and Michael Luck