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 9 - Shell Scripting 2 (page 1 of 2)

The Story So Far

Last episode started the shell-scripting tutorial; this and the next episode will continue it. I have covered shell variables, simple scripts, and 'if-else' statements. Now I will concentrate on the useful 'test' command used in many conditions, and introduce 'case' and looping control constructs.

Last episode parted with the observation that:

if [ "$1" = "" ]; then

is equivalent to :

if [ "$1" = "" ]
then

The two are equivalent because shell statements can be separated by a semicolon instead of a newline. For example:

$ cd ~; ls

is equivalent to:

$ cd ~
$ ls

Most scripters prefer this form:

if [ "$1" = "" ]; then

as it looks a little tidier.

Note that I am using "$" where previously I have used "%" to indicate the shell prompt. I will use "%" only when I am referring to the tcsh, and "$" when I am referring to sh, bash, etc.


What's in a Quote?

...or rather, what remains in a quoted string, and what does the shell interpret?

You will recall from the previous episode that in the statement:

combined="The number is $number, the filename is $filename, it is now $date"

the shell peeked inside the quotes to interpret '$' and thus expanded shell variables and parameters.

Consider this example script called 'quote':

$ cat quote
#!/bin/sh

variable="Hello"
echo "$variable from $0"
echo '$variable from $0'
echo

cost=24.50
echo "Cost = $$cost"
echo 'Cost = $$cost'
echo "Cost = \$$cost"

Executing it produces:

$ ./quote
Hello from ./quote
$variable from $0

Cost = 13233cost
Cost = $$cost
Cost = $24.50

The first echo behaves as expected. In the second, the use of single quotes instead of double quotes prevents 'sh' (or 'bash') from interpreting the $, and hence from expanding '$variable' and the script name '$0'. Single quotes are often used when a string is to be written to a file, or passed to another command, verbatim.

In the second part of the script, the first '$' in:

echo "Cost = $$cost"

is to be taken literally, but the second is to be interpreted as $cost. Neither single quotes nor double quotes will do what we want. In these situations, use double quotes and escape the dollar that is to be taken literally using '\$'. Notice the odd behaviour of $$ - this expands to be the process id of the shell. See the sidebar for more special parameters.

In the following example, backslash is used to escape double quotes. The first attempt will not work because the text 'Hello there' is outside of the double quotes:

$ var="He said "Hello there""
bash: there: command not found

so we escape the inner double quotes with a backslash:

$ var="He said \"Hello there\""
$ echo $var
He said "Hello there"

Note that a star (*) within double quotes is NOT expanded. Try:

echo *

versus:

echo "*"
 
Tell Me More...

Special Parameters

The shell recognizes $0 as the script name, and $1 onwards as the parameters. In addition, one can take advantage of the following expansions:

$* - expands to all the parameters passed to the script (but not the script name).

$# - expands to the number of parameters passed to the script

$$ - expands to the process id (pid) of the shell

For example:

$ cat ./sps
#!/bin/sh
echo $0 $1 $2 $3
echo $*
echo $#

Executing this:

$ ./sps one two three four

Gives:

./sps one two three
one two three four
4

Try This...

The first line of a shell script may be used to specify which shell is to execute the script. Mostly it will be:

#!/bin/sh

What appears after #! is interpreted as the name of the executable that will execute the script. It may be any executable capable of interpreting the script. (In fact it can be any executable at all.)

Try with #!/bin/ls.

Escaping

Generally, in Unix a backslash escapes (instructs to take literally) whatever follows it.


The Life of a Shell Variable

So far we have used shell variables that are local to a shell and persist only while that shell is running. A shell does not inherit variables from its parent, nor does it return the favour. Once a shell has completed execution, its variables are destroyed. Remember that when a script is executed a new shell is created. When the script completes the new shell terminates, its variables are destroyed, and control is returned to the original shell.

Consider the following example:

$ cat caller
#!/bin/sh
echo var = $var
var="Hello"
echo var = $var

(1)
$ var="goodbye"
$ echo $var
goodbye

(2)
$ ./caller
var =
var = Hello

(3)
$ echo $var
goodbye

Following this through:

(1) 'var' is set to "goodbye" in the interactive shell.

(2) The script 'caller' is executed, which does not inherit the value of 'var' from its parent shell, as seen by the line 'var = '.

(3) When the script completes, its particular 'var' (set to "Hello") is no more. But, notice that the instance of 'var' from the interactive shell has not been overwritten and is still set to "goodbye". The two 'var' variables may have the same name, but they are different variables that live in different shell executions.

The following example shows what happens to variables when one script (caller) calls another (callee).

$ cat caller
#!/bin/sh
var="Hello"
echo var = $var
./callee
echo var = $var

$ cat callee
#!/bin/sh
echo inner var = $var
var="goodbye"
echo inner var = $var

Execute 'caller', which will itself execute 'callee':

$ ./caller
var = Hello               <- from caller
inner var =               <- from callee
inner var = goodbye       <- from callee
var = Hello               <- from caller

Again, notice that the two 'var' variables are distinct. 'Callee' does not inherit the 'var' belonging to 'caller', nor overwrite it.

Set and Unset

The shell command:

$ set

displays the value of all shell variables currently defined. Setting a variable to "" will not destroy it, but simply set it to empty. Use:

$ unset var

to remove it completely.

You will notice that 'set' lists many variables, mostly in uppercase, which are termed environment variables.

Environment Variables

An environment variable is a shell variable that persists. It is set in one shell and can be read any another shell, script, or in fact any executable that was started by the original shell. The shell provides a means of setting and deleting environment variables, and is responsible for propagating them to sub-shells and executables. Many environment variables are set by the shell itself, and others by the login scripts executed by the shell (see part 11).

To set your own environment variable use:

$ VAR=hi; export VAR

By convention, but not necessity, an environment variable is named in upper case. The only difference compared to a standard shell variable is that it is marked for export to the environment when a script or executable is run. Consider this example:

cat caller
#!/bin/sh
echo VAR = $VAR
VAR=hi; export VAR
echo VAR = $VAR
./callee
echo VAR = $VAR

$ cat callee
#!/bin/sh
echo inner VAR = $VAR
VAR="goodbye"; export VAR
echo inner VAR = $VAR

Executing this:

(1)
$ VAR=hello; export VAR
$ echo $VAR
hello

(2)
$ ./caller
VAR = hello

(3)
VAR = hi

(4)
inner VAR = hi
inner VAR = goodbye

(5)
VAR = hi

(6)
$ echo $VAR
hello

Following this through:

(1) First, in the interactive shell, we set and echo environment variable VAR and get 'hello'.

(2) Next, we execute 'caller', which inherits the value of VAR from its parent shell.

(3) Caller then changes this value to 'hi' and echoes it, and calls 'callee'.

(4) 'callee' inherits VAR, displays it, and gives it a new value 'goodbye'.

(5) But now notice that in returning to 'caller' the value of VAR reverts to its old value of 'hi'.

(6) Similarly, when we return to the original interactive shell, VAR reverts to its original value of 'hello'.

This behaviour can easily be explained. When a new command is executed, it inherits the environment from its parent shell (i.e. it inherits all the environment variables, but not the regular shell variables). It is free to use and change the environment. However, when the process exits, the new environment is not passed back. The new command effectively gets a copy of the environment, not access to the original. (In programming terms, this technique represents 'pass by value' and not 'pass by reference' or 'copy back'.)

Well I Do Declare!

For those of you familiar with programming languages, shell and environment variables do not need to be declared before they are used. This saves a little time, but can cause problems in larger scripts. A mis-spelt variable is treated as a new and different variable to the one intended, and with no warning.


Next Page

On page 2 I will continue with control constructs - more on 'if', then 'case', 'for' and 'while' loops.


previous

Part 9 - Shell Scripting 2 (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.