Unix Lab



Shell Scripting 1


In this module we will look at what shell scripts are and how to write some simple ones. Shell scripting is a big topic and we can only peek at some of the capabilities. Hopefully, you will get excited enough about what you can do with scripts to find out more.

A shell script is just a file of Unix commands. The idea of a script is to have a set of commands that carry out some task. Whenever you want to carry out that task, all you have to do is tell the Unix shell to execute each of the commands in the script file.

For example, suppose you want to locate all the .class files in some directory and place them in a jar file. Perhaps you do this all the time for various directories and jar file names. You can write a script that will allow you to do this where you specify the directory and jar file name at the time you execute the script.

A script for reporting the value of the CLASSPATH variable

We will start with a very simple script.

Use a text editor and create a file called clpath containing the following lines of text (cut and paste)

#!/bin/sh

# File: clpath
# This script will report the current value of the CLASSPATH variable.

echo $CLASSPATH

We want to tell the Unix shell to execute this file. This means asking the shell to execute each of the lines in this file. First we need to discover what each line actually tells the shell to do.

Let's look at the clpath file. The first line of the file is:

#!/bin/sh

In script files, lines beginning with # are comments and are ignored by the Unix shell when it reads the file. In our example, there are three comments (blank lines are also ignored by the shell). The exception is that if the first line begins with the two characters #! then the remaining string on that line is taken to be the name of a program that will execute the remaining lines. Here, we will therefore be telling the shell to have the program /bin/sh execute the rest of the lines in this file. The program /bin/sh is none other than the Bourne shell.

Even though student accounts use the C shell by default, most Unix scripters prefer to use the Bourne shell for scripts. So we're telling whichever shell happens to be reading the script, that we want to let the Bourne shell execute the rest of the statements in the file.

How do we tell the shell to execute the script? How do you tell the shell to execute any command (e.g. grep). You just type in the name of the command. It's the same here. We will just enter the name of the script file at the command line.

Open a terminal window and in the directory containing your clpath file, type:

clpath

What do you see?

The problem is that all files that are to be executed must have execute permission. Use the chmod command to add execute permission for all.

Now try again by typing the name of the file and see what you get. If you have successfully added the execute permission, the result should be a disply of the value of your CLASSPATH variable.

Let's see why that is. Examine the other lines of the clpath file. The only one that is not a comment is the line:

echo $CLASSPATH

The echo command tells the shell to display the string that follows. We encountered the string CLASSPATH earlier in the context of compiling and executing Java programs. CLASSPATH is the name of one of the system variables that the shell keeps track of. The $ in front of the name signals that we want the content of the CLASSPATH variable (the actual string value) and not just the characters: CLASSPATH.

Modify the clpath file and remove the $ character from just before the word CLASSPATH. Then execute the file clpath. Do you understand the difference between the presence and the absence of the $ character?

A script to compile and execute a program

In this next script, we will put together the commands that will allow us to compile a Java program and then execute it. The name of the script will be cande (compile and execute) and the way we will use it is to type:

cande MyClass

This will result in the compilation of a file MyClass.java (and any other .java files that may also be compiled as a result) followed by the execution of the (assumed) main method in the MyClass class.

Use a text editor and create the file called cande containing the following lines of text (copy and paste)

#!/bin/sh

# File: cande

# Compile and execute the named Java file.

if [ $# -lt 1 ] 
then
   echo "usage: cande Source"
   echo "  where Source.java is the assumed name of the Java source file"
   exit 1
fi

javac $1.java
if [ $? -ne 0 ]   #--check if everything is OK
then
   echo "Error during compilation"
   exit 1
fi

# Getting here means compilation went fine. Now execute.
# The assumption is that the main method to execute is in
# the file named on the command line.

java $1

This file has a number of new things in addition to the things we have already seen before. You already know about the first line and other comments. Now we have an if statement. This is a bit strange because you probably didn't know that the Unix commands are actually part of a language that we are now using more like a programming language than before. The commands you are used to like: ls and chmod and cd and all those other commands you've seen are just some of the commands. The if command introduces the ability to control the flow of control in the execution of the script commands.

Here is the syntax of the if command:

if [ some test ] 
then
   some command(s)
fi

The end of the if command is the fi (if spelled backwards). The square brackets after the if are necessary and must have a blank before and after both the [ and the ] characters.

Let's look at the test in the cande file:

$# -lt 1

We've seen the $ before to mean the value of something but what is # refer to? Well, the shell keeps track of how many parameters the user has entered when she typed the current command (which should have been something like: cande MyClass) and we don't want to go ahead if the user forgot to type in the name of the class. So, $# means the value of the number of parameters. The -lt is the shell equivalent of the < operator in other programming languages like Java or C.

All together, the test asks: is the number of parameters less than 1. If so, we want to abort this script. This we do by placing the three statements you see after the then statement. The echo statements just display their appropriate strings. The exit 1 statement terminates the script and leaves a 1 as the indicator of the result of executing the script (1 means an error occurred).

Turn the cande into an executable file with the chmod command. Then type:

cande

You should see the error message resulting from the fact that we have not supplied a parameter. The convention among Unix programmers is to display a "Usage" comment that shows the user how to use this command correctly.

Continue examining the cande script. After testing for the lack of a necessary parameter, we move to the javac command. The command we want to issue is:

javac MyClass.java

where we are assuming as an example that the user entered MyClass as the name of her class that she wished to compile and execute.

The parameters to a Java script have numerical labels (1, 2, ...). If we want the value of those parameters we place a $ before the appropriate number. Notice how we embed the $1 into the command we next issue. The Unix shell will replace $1 with the value the user entered right into the command at that position.

Before we go further into this script we can try it out and see what we get so far. Start by creating a directory named graph and placing the files:

Connection.java
FindNeighbors.java
GraphUSAReader.java
graph.usa
into that directory. These files are available in the /handounts directory and were used in an earlier module.

In the graph directory type (the actual path shown will depend on the location of the cand file relative to the location of the graph directory):

../scripting/cande FindNeighbors

What do you get? What do you think the error message means? Actually, if you recall the FindNeighbors program from an earlier module, it examines a file of links between cities in the U.S. and searches for all neighbors of a given city. But we haven't specified a city so the program died.

How do we supply this city name? The simple answer is we supply that as a second parameter to the cande script.

Modify the last line of the cande script file so that it now reads:

java $1 $2
Then in the graph directory, enter the command:

../scripting/cande FindNeighbors Chicago

Now what happens?

There is still one more detail of the cande script. What happens if the compilation fails? We don't want to go on to try to execute a main method that doesn't exist. We would like to exit the script in that event. Here's how we do that.

The shell records a value whenever a command is executed. That value will be 0 if the command was successful and non-zero if the command failed. That value is available to us in scripts using $? as you see in the script. A non-zero value (-ne means not-equal) will result in a message and the execution of the exit 1 command.

How would you allow for more than one parameter to the main method? Suppose you needed two parameters? What happens if you provide for more parameters than you need? Will the script die?

How would you modify the cande script to allow you to place the class files generated by the compiler in a specific separate directory?


A script to create a jar file

Now that we have acquired some vocabulary in the shell scripting language, let's use that knowledge to create another simple script for gathering together all the .class files in a directory and creating a jar file with those .class files.

We envision the script named makejar and being used as in

makejar folderName jarFileName
where folderName is the name of any folder we wish to sweep for .class files and jarFileName is the name we want to assign to the jar file.

Copy and paste the following into a file named makejar using your text editor.

#!/bin/sh

# File: makejar

# This script will take two arguments. The first is the name of
# a directory, the second is the name of a file.
# The script will take all Java class files in the named directory
# and place them in a jar file with the name given as the second
# argument. 

# First check to make sure there are enough parameters
if [ $# -lt 2 ] 
then
   echo "usage: "
   echo "  makejar folderName  jarFileName"
   exit 1
fi

# We don't check to see if there are any class files in the named
# directory. If not then the jar command will fail.

jar -cf $2 $1/*.class


After you copy the file, make sure it has execute permission.

From the directory containing the script (let's assume it is in a directory named scripting which is a subdirectory in your home directory), type:

makejar ../graph graph.jar

What happens? Where did the jar file get placed? How would you check the contents of the jar file? Without modifying the makejar file, how would you ensure that the jar file will be placed in the graph directory?

How would you modify the makejar script so that, at the same time, it would create a tar file with all the source code for your Java program?


Click on to go back to the main directory.

Click on to take the quiz for this module.

These pages were developed by John Avila SJSU CS Department