DirectNET

Data Center Management Solutions including UPS Systems, Data Center Cooling, KVM over IP & IP Power Strips, Server Racks and Server Rack accessories; KVM Switches and KVM Extenders; Rackmount Monitors and Rackmount Keyboards.


NAVIGATION
Home
Store
INSIDE MAC
Television Shows
Broadcast Shows
Daily News Shows
Special Shows
EVENTS
DAILY TIPS
Design
Mac OS X
Mac OS X UNIX
COMMUNITY
Forums
Surveys
NEWS
Current
Press
Archive
FEATURES
Editorial
Dr. Mac
Reviews
Reader Reports
RESOURCES
FAQ
Documentation
Learning Center
MAN pages
Glossary
Tutorials
Tips
Links

OUR PARTNERS

OS X | UNIX

back

Unix

Mac OS X Unix Tutorial

by Adrian Mayo - Senior Editor for Mac OS X Unix, Janice Mayo - Senior Editor for Mac OS X Unix

Part 8 - Shell Scripting 1 (page 1 of 2)

The Story So Far

We have covered most of the commands and concepts one may encounter in day-to-day Unix. However, one major area remains untouched - that of writing shell scripts. In this and the next two parts, I will show you how to write your own shell scripts so you can automate oft-repeated command line tasks.

First off, Part 7 finished with the following observation - the differing output from these two similar commands.

% (cd / ; pwd) ; pwd
/
/Users/saruman

% cd / ; pwd ; pwd / /

In the first example, the bracketed commands are executed as a sub-shell: that is, a new shell is spawned to execute

(cd / ; pwd)

(Using ';' is a way of placing more that one command on the same line.)

Thus 'cd' and the first 'pwd' command are executed by a new shell, which then completes. The second 'pwd' command is then executed by the original shell, for which the current directory has not been changed.

In the second example, no sub-shell is spawned so the 'cd' command sets the current working directory of the main shell, and both 'pwd' commands reflect this.


What is the Shell?

Just to recap on earlier tutorials:

The shell in the programme that is running in your Terminal window: it is the shell that displays the prompt and waits for you to type a command. It then interprets what you've typed, goes away and does as you've asked, and returns for more. In Mac OS X the default shell, and therefore the one you are probably using, is 'tcsh' - the t-c-shell.

What is a Shell Script?

A shell script is a sequence of commands written in a shell scripting language. Usually the commands are put in a file, and the file is read by the shell to execute the script. The shell reads the commands from the file and executes them, in much the same way as it reads commands typed at the command line. Of course, the same file can be executed many times.

A shell script of any reasonable complexity will include both the familiar Unix commands (such as 'cd', 'ls', 'mv', and so on), and special commands that are part of the shell's own scripting language. These special commands control the flow of the script. For example, we can instruct the shell to rename a file if it exists, else display an error message to the user. Here the script has two actions, but only one of them is executed: which one depends upon some external condition.

The Two Duties of a Shell

A Unix shell has two duties:

1) To provide an interactive environment (the user interface). In this role we call it an interactive shell. This is where you type a command and the shell executes it. A good interactive environment will make your life at the command line easier. For example, the t-c-shell provides powerful command line editing and history (Part 2), current directory commands (Part 3), and re-direction and piping (Part 7).

2) To provide a shell scripting language. This is what we will cover in this and the next two tutorials. However, different shells have different scripting languages (see the side bar).

The Dilemma

Which shell scripting language do I teach?

The standard language for writing Unix scripts is that of the Bourne shell. Most scripts are written for Bourne, the start-up scripts must use it, many Unix commands and daemons assume it, and most shells will run it - 'sh', 'bash', 'ksh', and 'zsh'. Mac Os X provides all these shells.

The 'csh' and 'tcsh' run a different scripting language to all the others. Unfortunately, 'tcsh' is the default shell in Mac OS X. I say unfortunate because it is necessary to understand Bourne shell scripting whatever your day-to-day interactive shell is, so this is the one I will teach in these tutorials. I will not teach 'tcsh' scripting. However, I will give examples of 'tcsh' scripts along side the Bourne scripts.

Fortunately, it does not matter which interactive shell you are running when you execute a script, because a new shell is launched to execute the script. With a little "!#" magic, we can ensure that the new shell will be the correct one for the script.

 
Tell Me More...

Different Shells

There are many shells available in Unix.

The original shell was very basic, and was subsequently improved upon by the c-shell (called 'csh'), and the Bourne Shell (called 'sh').

The Bourne shell provided a good scripting environment, but lacked in the interactive environment. The c-shell was good for both, although most programmers preferred the Bourne shell for scripting. The scripting languages provided by 'sh' and 'csh' were incompatible.

New shells were written that provided a much better interactive environment.

- The t-c-shell ('tcsh')

This improved on both the interactive and scripting language provided by 'csh'.

- The Korn shell ('ksh'), Z shell ('zsh'), and the Bourne Again shell ('bash')

These improved on the interactive environment of the Bourne shell, whilst remaining compatible with its scripting language.

When writing shell scripts we therefore have the choice of two different scripting languages: that of csh/tcsh, and that of the Bourne shell and all the rest.


A Simple Shell Script

Here is a very basic shell script. All it does is invoke a few commands just as you would do interactively on the command line. It is so simple that it does not use any elements of a shell scripting language, and can therefore be executed by any shell.

Create a file called, well anything you like, for example 'test.sh'. The file should contain the following commands, one per line.

cd /
ls -al
cd /Users
ls -al
cat ~/test.sh

Now execute the script with (assuming your current working directory contains the script):

./test.sh

Did that fail? You probably got a permission denied error. That's because you tried to execute the script, but the file does not have execute permission. Add execute permission by issuing the command:

chmod +x test.sh

and check permissions with:

ls -l test.sh

Now try again:

./test.sh

and you should see a couple of screens of output flash by before the prompt re-appears. Examine the output and you can see that it is exactly as if you had typed the same the five commands interactively. You can page the output from the script by piping it to less:

./test.sh | less

Press return to reveal more output, and 'q' to quite out of 'less'.

Creating a File

You must use a text editor to write scripts. Perhaps the easiest to use is 'pico'. Type:

pico test.sh

Type in the text, hit control-o to write the file out, and control-x to exit pico. If you need to edit the file, use the cursor and delete keys much as you would with an Aqua-based editor. Trying to position the cursor using the mouse will not work.



Script Parameters

You will be familiar with passing parameters (or arguments, I will use both terms) to Unix commands. For example, in the following:

ls -l test.sh

we give 'ls' two arguments - '-l' and 'test.sh' These arguments must be obtainable by 'ls' itself to be of any use. The same applies to shell scripts.

Create a script file as below (I have used 'cat' to display the file on the Terminal):

% cat params.sh 
#!/bin/sh

#display parameters 1 to 3 echo Parameter 1 = $1 echo Parameter 2 = $2 echo Parameter 3 = $3
#display the script name echo Script name = $0

Let's examine this script.

Lines that begin with '#' are comments. They are ignored by the shell and you may follow the hash with any text you like. BUT - the first line of the script (and it must be the first):

#!/bin/sh

is special. It says which shell is to be called to the execute script - in this case /bin/sh (the Bourne shell). This allows us to write Bourne shell scripts and ensure they are run by 'sh' even if our interactive shell is a different one, such as 'tcsh'.

The next three lines display the first three parameters (arguments) passed to the script when it was executed.

echo Parameter 1 = $1

The echo command writes the text that follows to standard output. But note the $1 at the end. This is special. The Bourne shell interprets this as 'the value of parameter one' and replaces it with just that, before 'echo' is executed. Remember this: the shell interprets $1, not the echo command

Now run the script and pass it two parameters, any text, such as 'param1' and 'the-second-parameter'.

% ./params.sh param1 the-second-parameter
Parameter 1 = param1
Parameter 2 = the-second-parameter
Parameter 3 =
Script name = ./params.sh

The output from this script is the first two parameters, then a blank as $3 expands to nothing (we didn't pass a third parameter). The special parameter $0 contains the script name as invoked.

The tcsh

A script intended to be run by 'tcsh' instead of 'sh' must have the first line as:

#!/bin/tcsh

The above script will work in the 'tcsh' with no further changes.


 
Tell Me More...

When a Script is Executed

When we type

./params.sh

at the command line, your interactive shell takes a quick look at the start of the file, and sees the line:

#!/bin/sh

It then executes /bin/sh and passes 'params.sh' to it. It is the new shell that reads commands from 'params.sh' and executes them.

When the script completes, the new shell terminates and control passes back to the original interactive shell.

Why ./script-name?

Why precede the script name with ./ ? The interactive shell searches for Unix commands (which includes shell scripts) using the search path defined by:

% echo $PATH

This applies to scripts that you write yourself. If the current directory (assuming that is where your script is) is not in the search path, the shell will not be able to find the script. By using './script-name' to execute the script we are giving the full pathname of the script, and hence the shell does not have to refer to $PATH.

No Money ($0)

Execute 'params.sh' using the full pathname, eg:

/Users/you/params.sh

and observe the new value of $0 as displayed in the output from the script.

Shell Variables

Like any programming language, shell scripts allow one to define variables. A variable is somewhere to place a value - a number, some text, today's date, etc. A variable can be set at one place in a script and the value recalled later. The value of a variable can be changed or added to as many times as required throughout the script. The script parameters $1, $2, etc are examples of special variables, set automatically before the script starts executing.

A variable is set using the assignment operator '='.

For example:

% number=3

Sets a variable called 'number' to '3'. The value can be recalled using the '$' operator:

% echo $number
3

To try this interactively, you must be running 'sh'. See the side bar.

You can call your variables whatever you like. Start them with a letter and continue with letters, numbers, and underscores. The Bourne shell has a few reserved words that you must avoid using as variable names:

- if then elif else fi case esac for while until do done -

Write this simple script:

% cat vars.sh 
#!/bin/sh

number=3 filename=$1.$2 date=$(date)
combined="The number is $number, the filename is $filename, it is now $date"
echo $number
echo $filename
echo $date
echo 
echo $combined

and execute it:

% ./vars.sh letter doc

3 letter.doc Wed May 7 11:25:28 BST 2003
The number is 3, the filename is letter.doc, it is now Wed May 7 11:25:28 BST 2003

Variable 'number' is simply set to 3.

Variable 'filename' is set to the text that follows it. When executing such a statement, the shell firstly recalls the values of parameters and variables that are proceeded by the '$' operator, and then assigns the expanded value to the variable. In this case, the two parameters are expanded and 'filename' is set to the two parameters joined by a dot.

Variable 'date' is set to the current time and date. How does this work?

date=$(date)

executes the Unix 'date' command in a sub-shell and captures its output. The output is then assigned to variable, also, but not necessarily, called 'date'.

Any command or commands can be surrounded by brackets - try assigning the output from 'pwd', 'echo', and 'ls' to a variable.

% listing=$(pwd;echo "contains:";ls)
% echo $listing

Variable 'combined' is assigned the text that follows the '=' operator, after $number, $filename, and $date have been expanded. The double quotes are necessary because the text to be assigned contains spaces.

The tcsh

To set a variable use:

set number=3

To get the output from a command use back-quotes:

set listing=`(pwd;echo "contains:";ls)`

Run 'sh'

If you would like to try simple Bourne shell script commands interactively - ie on the command line - you must first run 'sh' (or 'bash', 'ksh', or 'zsh'). You are probably running the Mac OS X default shell 'tcsh'. To change to the Bourne shell type:

% sh

and you will be talking to 'sh' instead of 'tcsh'.

For example, try this:

% sh
sh-2.05a$ ls=$(ls)
sh-2.05a$ echo $ls
... listing of current ...
... directory follows ...

'sh-2.05a$' is the default prompt displayed by 'sh'.

Type:

sh-2.05a$ exit

to exit 'sh' and return to 'tcsh'.

Instead of 'sh', you may use 'bash', 'ksh', or 'zsh' as they all understand the same scripting language.

Vanishing Variables

The variables set in a shell script persist while that script is executing. When the script finishes, variables vanish and cannot be recalled on the command line, or by other scripts.

Special variables called 'Environment Variables' do persist, and we will discuss those in the next episode.

Spaced Out

It is VERY important to realise that spaces are significant. The statement:

number = 3

will not work. There must be no spaces either side of the '=' operator.

Similarly:

name=Adrian Mayo

will not work, because of the space after Adrian. Use double quotes as in:

name="Adrian Mayo"

when a value contains spaces.

TIP

If in doubt, enclose the text to the right of the '=' operator with double quotes.


Next Page

On page 2 I will introduce shell control flow with the IF statement, and give a few simple but useful shell scripts.


previous

Part 8 - Shell Scripting 1 (page 1 of 2)

next

Copyright © 2000-2008 Inside Mac Media, Inc. All rights reserved.
Apple assumes no responsibility with regard to the selection, performance, or use of the products or services. All understandings, agreements, or warranties, if any, take place directly between the vendors and prospective users.
Apple, the Apple logo, Mac, PowerMac G4, PowerMac G5, Xserve, Xserve RAID, PowerBook, iBook, Airport, AirPort Extreme, iMac, eMac, iLife, iMovie, iCal, iPhoto, iTunes, QuickTime, FireWire, iPod, iSight, AppleWorks, Macintosh, Jaguar, Panther, Mac OS, Mac OS X and Mac OS X Server are trademarks of Apple Computer, Inc.