Chapter 16

Shell Scripts - Variables

Introduction

We have learned about parameters in shell scripts; now we learn about variables.

Variables

Shell variables are a bit like parameters - they both can hold values which consist of zero or more characters. The differences between parameters and variables are:

Assigning values to variables

Variables are most useful in scripts but we will demonstrate them using the shell interactively. This example puts a value in a variable called file:

$ file=precious
$

and this example displays the value that is stored in file.

$ echo the value in file is $file
the value in file is precious
$

Notice that in the first example, there is no dollar sign in front of file but in the second one there is. This is because the dollar sign means `the value of', so file is the name of the variable, but $file is the value of the variable.

Now you can see why we never use parameters without a dollar sign in front of them - we only use the value of parameters and we rarely put new values into them inside scripts.

Empty variables and removing values

Variables do not have to be declared. If they haven't been given a value shell treats them as if they held nothing. This demonstrates that:

$ echo the value in nonesuch is $nonesuch.
the value in nonesuch is .

Notice that both the space before $nonesuch and the full stop after it were echoed but nothing was output in place of the empty variable.

We can remove the value held in a variable like this:

$ file=
$ echo the value in file is $file.
the value in file is .
$

Spaces and =

Beware of trying to put spaces around the equals sign when assigning values to variables. This shows what happens if you try:

$ answer = 42
sh: answer: not found
$

Shell is trying to execute a command called answer with arguments = and 42. If we just put a space after the equals sign, like this:

$ answer= 42
sh: 42: not found
$

shell empties answer and attempts to run a command called 42.

Uses for variables

What can we do with variables? The main use is in more complicated scripts but here are two simple cases where variables make life easier.

Suppose we want to keep moving files from one directory to another and that the path-names of the directories are both very long. We can save ourselves time and effort by putting the name of one of the directories into a variable, like this:

$ other=/homedir/cms/ps/student/unix/examples
$

and then we could refer to the other directory with much less typing in this fashion:

$ ls $other/scripts
/homedir/cms/ps/student/unix/examples/scripts: No such file or directory
$ mv scripts $other
$

Another use is to make the bu script easier to read. Here is an improved version:

$ more bu
file=$1
cp $file $file.bu
echo $file backed-up in $file.bu
$

The first step in the new bu puts the first parameter into file so that the reader no longer has to remember that $1 contains the name of the file. That little change makes the script much clearer. Obviously, the more parameters we have, the more useful the technique is!

Read

Nearly every programming language has a statement to output (display or print) variables and/or strings in the form of text. They also have another statement to input values into variables.

Shell scripts output text with echo. Shell's input statement is read which takes words from a line of input and puts them into variables. Here is a script which demonstrates the read statement:

$ more echoline
echo Type a line:
read line
echo the line was: $line
$

Executing it:

$ echoline
Type a line:
Small is Beautiful
the line was: Small is Beautiful
$

shows a line of input being read into a variable and displayed.

If we want to extract individual words we can do so:

$ more echoword3
read word1 word2 word3
echo word3 is: $word3
$ echoword3
E F Schumacher
word3 is: Schumacher
$

If more words are supplied in the input than there are variables in the read command, shell adds the extra ones onto the last variable:

$ echoword3
Small is Beautiful by E F Schumacher
word3 is: Beautiful by E F Schumacher
$

Avoid interactive scripts

When they learn about the read command, newcomers to Unix often use it in shell scripts instead of expecting arguments on the command line. This is unwise as it means their scripts can only be used interactively; they can't be used in further tool-building. Very few of the standard Unix commands are interactive. You should write your scripts in the same way, to make them fit in with the Unix philosophy.

Problems with echo

There are many different versions of Unix and we should write scripts that, as far as possible, run under any version. Possibly the biggest obstacle to portability is echo itself. This is partly because there are several different versions and partly because echo is also built-in to most shells to aid efficiency. The difficulty is there are subtle differences between these versions. My advice is: only use echo for complete lines of plain text. If you wish to use control characters, or output a partial line (without a newline), use the printf command.

printf

C programmers will be familiar with printf. To see all it can do, you will need to consult its man page. This example shows the main points:

$ cat format
printf 'A user prompt: '
read reply
printf '%-10s ' Tom
printf '%12s (Mr)\n' Cholmondeley
printf '%-10s %12s (Ms)\n' Cassandra Short
$

Executing the format script, we see this:

$ format
A user prompt: user reply
Tom        Cholmondeley (Mr)
Cassandra         Short (Ms)
$

The first printf outputs some text but leaves the cursor at the end of the line so the user can type their reply on the same line as the prompt.

Usually, printf is used to fit values into a format string. The second printf statement demonstrates that. The format string is enclosed in quotation marks and the value is Tom. In the format string, the percent sign (%) is a place marker; it shows where the value is to be inserted into the format string before output. The s, four characters after the percent sign, indicates that the value is a string of characters. The 10 between the % and the s gives the number of characters the value should occupy in the line of output. The - shows the value is the be placed at the left side of the allocated space (left justified). The third printf shows a right justified twelve character string which is followed by a newline character (\n). The last printf simply shows there can be more than one place marker in the format string, and that there can be more than one value. Notice, there can only be one format string.

As well as being more portable, printf helps us to have tidier output. Look how the left and right margins are lined up when format outputs the names.

Built-in variables

There are some pre-defined variables that shell already has values for. The names of these built-in variables are all in upper-case letters so that they do not conflict with the names we might choose.

Two of the most amusing ones are called PS1 and PS2 which hold the primary and secondary shell prompt strings. You can echo them thus:

$ echo $PS1 $PS2
$ >
$

As you see, on my system they are set to $ and >. The amusing bit is: you can set them to whatever you like - just like ordinary shell variables. Here is how it's done:

$ PS1='Yeah? '
Yeah? PS2='gimme more! '
Yeah? echo 'one
gimme more! two
gimme more! three
gimme more! lines'
one
two
three
lines
Yeah? PS1='$ '
$ PS2='> '
$

There are a couple of points to watch out for. First, it looks a complete mess if you don't end the prompt string with a space. Also, strange prompts are confusing, so only use them when there is a real need.

A better way to see shell's built-in variables is to use set which will display them all - user-defined and built-in. On my system part of the output looks like this:

$ set
DISPLAY=:0.0
EXINIT=set showmode
HOME=/homedir/cms/ps
IFS=

LD_LIBRARY_PATH=/X11/lib:/ext01/IXI/lib
LOGNAME=cmsps
MAILCHECK=600
MANPATH=/usr/local/man:/usr/lang/man:/usr/man:/X11/man
OPTIND=1
PAGER=more
PATH=/usr/local/bin:/usr/lang:/X11/bin:/usr/ucb:/usr/bin:.
PRINTER=lp
SHELL=/bin/sh
TERM=xterm
TERMCAP=vs|xterm|vs100|xterm ....... etc ...........
USER=cmsps
WINDOWID=25165837
XLIBDIR=/ext01/IXI/lib/X11
$

Of these, two of the most important ones are HOME and PATH.

HOME

You should recognise the contents of $HOME as being the name of your home directory. We can use it as a shortcut to avoid remembering or typing the home directory's full name. For example, to access the home directory when we are not in it, we can do this:

$ pwd
/homedir/cms/ps/book.unix
$ ls $HOME
bin
book.c++
book.pascal
book.unix
jobs
lastdir
logins
macro
$

Some versions of the shell let you type a tilde (~) character instead of $HOME. Try the following to see if it works on your system:

$ ls ~
????
$

If it doesn't work you could switch to bash.

PATH

PATH is more complicated: it contains a list of directory names separated by colons (:). The list of directories in the PATH shown above (/usr/local/bin:/usr/lang:/X11/bin:/usr/ucb:/usr/bin:.) is:

/usr/local/bin
/usr/lang
/X11/bin
/usr/ucb
/usr/bin
.

Whenever you enter a command, shell has to find a file with the same name as the command. It looks for the file in the directories listed in PATH. If it does not find an executable file in the first directory from the left in $PATH, it looks in the next one, and so on. If a file with the same name is not found in any of the directories, then you get the familiar xyz: not found error message.

The dot at the end of the list of directories means the current directory. That is why shell could not find newcmd when we were `hiding' in the elsewhere directory in the previous chapter. Shell was looking in elsewhere and the other directories in the list above, but the file wasn't in any of them. We will set up a customised version of PATH in a later chapter ?? and then we will be able to execute our scripts whatever directory we are in.

QUESTIONS

For first three questions you have to write a shell script. Name them `q11.1' ...

  1. Write a shell script to put the value 42 into a variable and display both the name and the value of the variable.

    Answer

    $ more q11.1
    answer=42
    echo The answer to the ultimate question is $answer.
    $ q11.1
    The answer to the ultimate question is 42.
    $
    

    Hide

  2. Modify the script from question one so that that it then removes the value in the variable and displays it again.

    Answer

    $ more q11.2
    answer=42
    echo The answer to the ultimate question is $answer.
    answer=
    echo The answer to the ultimate question is now $answer.
    $ q11.2
    The answer to the ultimate question is 42.
    The answer to the ultimate question is now .
    $
    

    Hide

  3. Create a shell script to prompt for a directory name, and then display the number of things (files plus directories) in the directory.

    Answer

    $ more q11.3
    printf 'Directory? '
    read directory
    cd $directory
    ls | wc -l
    $ q11.3
    Directory? red
    21
    $
    

    Hide

  4. Start a spare xterm and set the shell prompts to something amusing. Note that you can't do that with a script. Why not?

    Answer

    $ PS1='hoho '
    hoho PS2='heehee '
    hoho da\
    heehee te
    Wed Jul  8 22:19:11 BST 2009
    hoho
    

    Notice: the date command is split over two lines so the effect of the second prompt can be seen.

    You can't set the prompts with a script because the non-interactive shell that runs the script is a different shell to the interactive one that is prompting you and reading your keyboard input.

    Hide

  5. Reset the shell prompts to normal.

    Answer

    hoho PS1='$ '
    $ PS2='> '
    $ da\
    > te
    Wed Jul  8 22:19:59 BST 2009
    $
    

    Hide

ANSWERS

In the first three answers the script is displayed using more; then it is executed by typing its name.

  1. $ more q11.1
    answer=42
    echo The answer to the ultimate question is $answer.
    $ q11.1
    The answer to the ultimate question is 42.
    $
    
  2. $ more q11.2
    answer=42
    echo The answer to the ultimate question is $answer.
    answer=
    echo The answer to the ultimate question is now $answer.
    $ q11.2
    The answer to the ultimate question is 42.
    The answer to the ultimate question is now .
    $
    
  3. $ more q11.3
    printf 'Directory? '
    read directory
    cd $directory
    ls | wc -l
    $ q11.3
    Directory? red
    21
    $
    
  4. $ PS1='hoho '
    hoho PS2='heehee '
    hoho da\
    heehee te
    Wed Jul  8 22:19:11 BST 2009
    hoho
    

    Notice: the date command is split over two lines so the effect of the second prompt can be seen.

    You can't set the prompts with a script because the non-interactive shell that runs the script is a different shell to the interactive one that is prompting you and reading your keyboard input.

  5. hoho PS1='$ '
    $ PS2='> '
    $ da\
    > te
    Wed Jul  8 22:19:59 BST 2009
    $