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

Parameter expansion

We have already considered assigning values to variables in the previous chapter. In this section, we look at the shell's features that allow it to examine in detail whether variables have been set values and what form those values take.

Often you will write scripts where you will use variables that you assume will have certain values. They may be variables you have created yourself for your own benefit, or they may be 'system' variables, such as PATH, which have been set for you. However, there is always a possibility that such a variable has not been assigned a value. A case in point is the variable NAME, which is not mentioned in the POSIX standard, and commonly contains the user's real name. Many shells and utilities (especially mailers) use it, and it's quite reasonable to assume that it has been set a value. Unfortunately, this is not guaranteed.

It is thus good practice whenever writing a script that relies on a variable not defined as necessarily existing in POSIX, to check that it has in fact been assigned a value, and that that value is of the correct format. Parameter expansion is the mechanism usually employed.

Consider NAME, and suppose a particular script requires it; we could include the following code to check whether it indeed does have a value, and if not we could give it a default value:

if [ -z "$NAME" ]
then NAME="A.N. Other"
fi

This will work. It is also verbose - a script that uses many variables would be tedious to write if you include checks for all the variables in the script. It should be emphasised that it is a very good idea to check that variables have in fact been assigned values before you attempt to use those variables. Parameter expansion will not do anything that cannot already be done using test, but it provides a concise and easy to read notation that avoids saturating scripts with tests.

At this point we need to discuss an apparently minor - but nonetheless important - feature of variables. If a variable has not got a value, this can be for two reasons. Either it has not been mentioned at all before, in which case it is unset, or it has been set but has the null string (a null string "" has length zero) as its value, so

NAME=""

or, alternatively, since it would not be ambiguous:

NAME=

For most purposes the two situations have the same result. If you wish to unset a variable rather than just set its value to null, use unset:

unset NAME

To ensure that a variable is set, the form is

${variable:-default }

which expands to the value of variable, if that variable has been set or is null, otherwise to default. For instance, instead of the test example above, the first time you use NAME, replace $NAME by

${NAME:-"A.N. Other"}

The following script will check to see if variable NAME has been set; if not it will be replaced by the value of LOGNAME, and display a welcome message:

echo Hello ${NAME:-$LOGNAME}

Try this, first of all without NAME set, and then after you have given it a value. The form of default can be anything that returns a value - the above could be accomplished equally well using:

echo Hello ${NAME:-$(logname)}

Worked example

Create a welcome message to initially check variable NAME to find out your name, if it is unset, checks LOGNAME, and if LOGNAME is unset uses command logname as a last resort.
Solution: As in the example above, if NAME is unset we fall back on the value of LOGNAME, but then we also have to check that LOGNAME has been assigned a value. So we can replace $LOGNAME by the result of running the command logname.

echo Hello ${NAME:-${LOGNAME:-$(logname)}}

If a variable is unset, the :- mechanism will not assign the default value to it - that default is merely substituted for the expression at that single instance. If you also wish the variable to be set to the default, use := instead of :-, so:

unset NAME
echo Hello ${NAME:=$LOGNAME}
Hello chris
echo $NAME
chris

Another behaviour that might be desirable is for the shell to give you an error message if a variable is unset - this is especially useful if there is no sensible default value you can substitute for the variable. Replace :- with :? so:

unset NAME
echo Hello ${NAME:?}
NAME: parameter null or not set

If you follow the ? with a string, that message will be displayed instead of parameter null or not set:

echo Hello ${NAME:?"who are you?"}
NAME: who are you?

Worked example

Ensure that PATH is set; if it is not, reset it to /bin:/usr/bin, and inform the user of its value.
Solution: Use positional parameters

echo The PATH is ${PATH:="/bin:/usr/bin"}

When using :- the default value is substituted if the variable is null or unset. If you use :+ the reverse happens - the default value is substituted only if the variable is set and not null:

unset NAME
echo ${NAME:+Chris}

(blank line)
echo ${LOGNAME:+Chris}
Chris

We can discover the length (i.e. the numbers of characters) of a string:

echo $LOGNAME
chris
echo ${#LOGNAME}
5

Note that # does not begin a comment when used in this way.

Worked example

Use a loop to print out a line of 50 plusses so:
++++++++++++++++++++++++++++++++++++++++++++++++++
Solution: Use an until loop, and store the plusses in a variable, LINE (say). Start off by setting LINE to null, and repeatedly add a single + to it until its length has become 50.

LINE=""                    # Set LINE to null
until [ ${#LINE} -eq 50 ]  # Until its length is 50 ...
do
  LINE=$LINE+              # add another "+" to it ...
done
echo $LINE                 # and finally display the line

Copyright © 2002 Mike Joy, Stephen Jarvis and Michael Luck