Chapter 14

Shell Scripts

Introduction

So far, we have only seen examples where Unix commands were simply typed at the terminal in response to the $ prompt. In this chapter we will see examples where the Unix commands are stored in a file and are executed again and again without our having to re-type them. When we have put some commands into a file, we can use the name of the file as a new command. This means that we can create our own commands which work like the built-in commands we saw in earlier chapters. An executable file containing Unix commands is called a shell script, or simply, a script. Shell scripts are one of the most powerful and useful facilities of Unix.

Our very first script

This command puts some text into a file called newcmd:

$ cat > newcmd     # do NOT do this
date
pwd
ls
^D
$

Putting text in a file is best done with vi or one of the other Unix editors. However, for very small files, using cat and output redirection is simpler. Also, for the purposes of this chapter, it is easier to follow.

Now we have created the file, we can display it:

$ more newcmd
date
pwd
ls
$

As you can see, it contains three Unix commands, one per line.

We can now execute the commands in newcmd by entering this command:

$ sh newcmd     # do NOT do this
Mon May 23 11:25:42 BST 1994
/homedir/cms/ps/book.unix
chapter1
chapter2
chapter3
chapter4
newcmd
$

Notice that the three commands are executed in the order in which they occur in the file. Also, note that the output from the three commands is joined together; unless we know what output to expect, we cannot tell which lines are from which command. In fact, the first line of output is from the date command, the second is from pwd and the remainder are from ls.

The sh newcmd command is unfamiliar but not all that strange. As we saw in Chapter ??, sh is the name of the shell program - the program that processes the commands we type in at the keyboard. So our command makes Unix run sh and tells sh to process the commands in the newcmd file. If we had forgotten to put the name of a file after sh, the shell would have tried to get its commands from the keyboard - which is what it usually does.

You might ask: if sh is already reading our commands from the keyboard, why do we have to start it? The answer is: we are starting another copy of sh which is quite separate from the one that is processing our keyboard commands. We need to give the two copies names to distinguish between them. We will call the shell that is accepting commands from the keyboard the interactive shell; we will call the other the non-interactive shell. The connection between them is that the interactive shell starts off the non-interactive shell and waits for it to finish. When the non-interactive shell gets to the end of the newcmd file, it terminates. This is what the interactive shell has been waiting for; it displays the $ prompt and waits for the next command to be entered at the keyboard.

As you should by now expect with Unix, you can use redirected input instead of, or as well as, a file name. For example, we could use:

$ sh < newcmd     # do NOT do this
Mon May 23 14:41:35 BST 1994
/homedir/cms/ps/book.unix
chapter1
chapter2
chapter3
chapter4
newcmd
$

In this case, the alternative is longer and we will not use it. The point is that sh is not totally different to the other commands; it can get its input from anywhere - even from a pipe.

The . (dot) command

There is an alternative way to execute the commands in newcmd - here it is:

$ . newcmd     # do NOT do this
Mon May 23 13:18:41 BST 1994
/homedir/cms/ps/book.unix
chapter1
chapter2
chapter3
chapter4
newcmd
$

The dot is a command that tells the shell to switch to another file and process the commands it contains. At the end of the file, the shell reverts to getting commands from its original source of input. The difference between this method and the one before is that only one shell is involved - there is no non-interactive shell. This difference has some quite subtle implications which we will return to later ??.

By the way, the space after the dot is necessary; all Unix commands have white space between them and what follows.

Look no command

It would be tedious to have to keep typing either sh or a dot in front of the file-name every time we wanted to execute the commands in a file. Fortunately we don't have to. If we make the file executable, Unix will automatically start a non-interactive shell to process the commands in the file whenever we use the name of the file as if it were a command. For example:

$ chmod 700 newcmd
$ ls -l newcmd
-rwx------  1 cmsps          12 May 23 11:21 newcmd
$ newcmd
Mon May 23 13:34:53 BST 1994
/homedir/cms/ps/book.unix
chapter1
chapter2
chapter3
chapter4
newcmd
$ newcmd
Mon May 23 13:34:55 BST 1994
/homedir/cms/ps/book.unix
chapter1
chapter2
chapter3
chapter4
newcmd
$

In the example, the chmod command makes the file executable and the ls -l checks that we did it properly. Next, the newcmd file is executed twice. The reason for executing it twice is to show that the chmod only has to be done once ever. The script can be edited and Unix will not "forget" that it was made executable.

Now that we can use the filename on its own, newcmd really does look like a new command. This is the main purpose of shell scripts.

Our script is just like the other commands

This new command of ours can even have its output redirected - just like the standard Unix commands. For example:

$ newcmd > output
$ more output
Fri May 27 14:34:44 BST 1994
/homedir/cms/ps/book.unix
chapter1
chapter2
chapter3
chapter4
newcmd
output
$

Similarly, we can send the output from newcmd via a pipe to another command. In this example the output is sent to the wc command:

$ newcmd | wc
       8      13     105
$

Later, we will see that we can redirect a script's input as well as its output.

Our script seems bashful!

There is one apparent difference between our new command and the standard ones. We can only execute newcmd if it is in our working directory, as this shows:

$ mkdir elsewhere
$ cd elsewhere
$ newcmd
newcmd: not found
$ cd ..
$

Shell appears only to look for newcmd in the current directory. Since newcmd is in the directory above elsewhere, it is not found.

This is not much of a problem yet, but it would be, if we tried to write scripts that changed to other directories. We will see how to cure this little problem in a later chapter.

Shell is a complete programming language

Readers who have already learned a programming language may like to know that all the facilities you would expect to find in a programming language exist in the shell language. The difference between a shell script and an ordinary program is that:

Programs and scripts both provide the following facilities:

Writing and running the newcmd script has already demonstrated two of these facilities. First, it contains a sequence of commands which are executed one after another. Second, the dot command makes the shell switch from one file to another - rather like #include in the C programming language.

QUESTIONS

Many of the questions in this tutorial depend on you remembering (or digging out) the answers to simple problems concerning tool-building and pipes. For most of the following questions , you have to write a shell script. Name them "q9.1" etc.

  1. Create a shell script to output a single number that is the number of things in your current directory Do not worry whether the "things" are files or directories.

    Answer

    $ more q9.1
    ls | wc -l
    $ q9.1
    42
    $
    

    This is a useful script to have if it took you a long time to work out how to do it. You have encapsulated the knowledge for later use. Of course, remembering the name of the script might be a bigger problem!

    Hide

  2. Create a shell script to give a title, followed by: the date, a listing of the directory, and the number from question one. (By listing of the directory we mean a list of the things in the directory.)

    Answer

    $ more q9.2
    echo 'These are the files on'
    date
    ls
    q9.1
    $ q9.2
    These are the files on
    Mon Jul 13 21:29:14 BST 2009
    flag blood
    2
    $
    

    Notice that we don't duplicate the existing code from q9.1. That would be re-inventing the wheel and lead to maintenance problems.

    Hide

  3. Without changing "q9.2" or creating a new script, put the output from (2) into a file.

    Answer

    $ q9.2 > file
    $
    

    Notice we didn't have to write a script for this; we simply did what you would do with any Unix command.

    Hide

  4. Create a shell script to count the directories in the current directory.

    Answer

    $ more q9.4
    ls -l | grep '^d' | wc -l
    $ q9.4
    2
    $
    

    Again we have encapsulated something we might have had to work out so that we can simply re-run it.

    Hide

ANSWERS

  1. $ more q9.1
    ls | wc -l
    $ q9.1
    42
    $
    

    This is a useful script to have if it took you a long time to work out how to do it. You have encapsulated the knowledge for later use. Of course, remembering the name of the script might be a bigger problem!

  2. $ more q9.2
    echo 'These are the files on'
    date
    ls
    q9.1
    $ q9.2
    These are the files on
    Mon Jul 13 21:29:14 BST 2009
    flag blood
    2
    $
    

    Notice that we don't duplicate the existing code from q9.1. That would be re-inventing the wheel and lead to maintenance problems.

  3. $ q9.2 > file
    $
    

    Notice we didn't have to write a script for this; we simply did what you would do with any Unix command.

  4. $ more q9.4
    ls -l | grep '^d' | wc -l
    $ q9.4
    2
    $
    

    Again we have encapsulated something we might have had to work out so that we can simply re-run it.