A quick tourWe'll look at a script which contacts a number of ftp machines, and gets back any one of a list of files if which exists on that machine. This will demonstrate using variables in scripts. This script starts of trivially simple, and we then make a few elaborations on it.

Points covered

This script uses the program ncftp, which can copy a given file from an ftp server back to the World. The syntax, to get a file named snoopy.zip from a directory named /pub/pilots on a server named ftp.sopwith.com, is:
world% ncftp ftp.sopwith.com:/pub/pilots/snoopy.zip
(In the example scripts, we'll explicitly call what is, at this writing, the newest version of ncftp, ncftp-2.3.0, which works "cleanly" if the file it tries to get off a remote ftp server does not exist.)

The simplest script is just a command

We'll start with the command above as an example. Make a file (a script is just a file with one or more valid commands, set to be executable) with the editor of your choice. If you're going to use pico:
	world%  pico getsnoopy

Then, in the file, put in the command you want to run:
	ncftp ftp.sopwith.com:/pub/pilots/snoopy.zip

then save and exit, and make the file executable with this command:
	world%  chmod +x getsnoopy

Now, you can just run the command with

	world%  getsnoopy


Telling UNIX to use sh

Different "shells" in UNIX use different syntax. It's not relevant in this example, but from this point forward, we'll specify which shell is to be used, by adding the command
	#!/bin/sh

At the very beginning of every shell script. (The # has to be the first character in the file, there can't be even a blank line before it.) Now, when you run a script, it will first fire up a program named "sh", the bourne shell. The bourne shell will read and execute the rest of the commands in the file.

Using comments

Except in the special case of its part as a directive to use "sh", the "#" sign is used to signify a comment. Text following a # in a script is ignored by the script. So, let's add a comment to our script, so we remember what it's supposed to do, if we forget about it for six months. Our script grows to three lines:
	#!/bin/sh
	# get that snoopy file from the sopwith server
	ncftp-2.3.0 ftp.sopwith.com:/pub/pilots/snoopy.zip

Using variables

Now, let's change the script to check on more than one server. This illustrates using variables. In shell, a variable assignment looks like:

	SERVER=ftp.volkswagen.com

To get a use a the value of a variable in a script, you write "$NAME", substituting the name of the variable for "NAME". (Variable names don't have to be all uppercase, but they traditionally are. Since commands are usually given all lowercase names, the variable names stand out. So, anywhere in a script we write "$SERVER", we'll get "ftp.volkswagen.com". Besides saving typing, that's not interesting, unless we have some way to make the contents of the variable change. The "for/in/do/done" construct is one such method.

If we write:

	for $SERVER in ftp.washington.edu ftp.volkswagen.com ftp.ftp.com 
	do
		some-commands-that-use-the SERVER variable
	done

The commands between "do" and "done" will be executed repeatedly, with "SERVER" set to each of the three server names. Now, a complication comes up. We want to use a variable's value inside a larger string. If we just wanted our script to run a command like:
	ncftp  ftp.washington.edu


We'd just say "ncftp $SERVER". Instead, we want something like:

	ncftp  ftp.washington.edu/pub/pilots/snoopy.edu

In such cases, we put the name of the variable, but not the $, in curly braces. sh (the program executing the script), knows that that means "the variable name starts and stops here". (Actually, unless the variable name is immediately adjacent to a character that could be part of a variable name, the curly braces are not necessary. Letters, digits, and underscores are the only characters that can appear in variable names.) E.g.,
	ncftp  ${SERVER}/pub/pilots/snoopy.edu


So, here's our script

	#!/bin/sh
	#try to get snoopy off a few file servers
	#first, set a variable to the name of the file we want to get
	FILENAME=snoopy.zip
	for SERVER in 	ftp.washington.edu ftp.volkswagen.com ftp.ftp.com 
	do
		ncftp-2.3.0 ${SERVER}:/pub/pilots/${FILENAME}
	mv $FILENAME  snoopy.from.${SERVER}.zip
	done

That script will contact each of the servers in the list and snag the file. The mv command will rename the file after it's been gotten, so the one we get from the next server won't clobber it. The general syntax of "for" is:
	for VARNAME in word1 word2 word3 word4
	do
		somecommands
	done

"somecommands" will almost always reference the variable "$VARNAME" in practice (else why set it?), but that's not syntactically imposed. An example of a for loop:
	for COLOR in red blue green
	do
		rm code-$COLOR
	done
That loop would try to remove three files, "code-red", "code-blue", and "code-green".

Telling the user what you're doing.

The commands above won't give any output unless they're an error (e.g., if the script lists servers that don't exist). You can use the echo command in your script to tell you what's step it's at: In the script above, in the first line after "do", you could insert an echo command, making the "for" loop look like:
	for SERVER in 	ftp.washington.edu ftp.volkswagen.com ftp.ftp.com 
	do
	echo about to get $FILENAME from $SERVER
		ncftp-2.3.0 ${SERVER}:/pub/pilots/${FILENAME}
	mv $FILENAME  snoopy.from.${SERVER}.zip
	done

Does a file exist

If the ncftp command fails, there won't be any file to rename. That's harmless enough in this case, you'd just get an error message. But for purposed pedantic and aesthetic, here's how we'd avoid trying to rename the file if it wasn't there.
	if [ -f $FILENAME ]
	then 
	  mv $FILENAME  snoopy.from.${SERVER}.zip
	fi

"fi" isn't a typo, it's "if" spelled backward, and ends an "if block". The text between "if" and "fi" will only be executed if the file named as the contents of the variable FILENAME exists. Any programmer unfamiliar with shell would assume that the notation: "[ -f something]" was part of shell's syntax. That's not the case. In fact, "[" is the name of a program, which is also called test. You usually see "if" followed by "[...something...]", but that's not necessarily the case. "test", a.k.a "[", is one of several mini-languages shell programmers learn, but not technically a part of the shell.

If / then / else in general

As in most other languages, the "if" control statement can result in two courses of action. We can write:
	if  expression
	then
	   some commands
	else
	   some other commands
	fi

In the case of our script, we want to tell the user if the file doesn't exist. So, we can re-write the "if" part of the script to look like

	#!/bin/sh
	#try to get snoopy off a few file servers
	#first, set a variable to the name of the file we want to get
	FILENAME=snoopy.zip
	for SERVER in ftp.washington.edu ftp.volkswagen.com ftp.ftp.com 
	  do
	     ncftp-2.3.0 ${SERVER}:/pub/pilots/${FILENAME}
	  # did we successfully get the file?
	  if [ -f $FILENAME ] 
	  then
	     # we got it! rename it and tell the user
	     mv $FILENAME  snoopy.from.${SERVER}.zip
	     echo got it from $SERVER, saved as snoopy.from.${SERVER}.zip
	  else						
	     # oops, file doesn't exist, tell the user
	  echo could not get $FILENAME from $SERVER
	  fi
	done

A few random notes

Reading in a list of servers

The command "read" reads from standard input. A common use is to say something like:
	echo "Do you really want to erase ${FILENAME}?"
	read ANSWER

That will stop to read an answer from the user, and put whatever the user types into the variable ANSWER. (You can use 'expr', or 'grep', or several other utilities to decide whether the answer was a yes or a no.)

In our script, we're going to read server names out of a file. Most commonly, scripts read from standard input, that is, the keyboard. Here, we show one way to read from a file. The notation is:

	exec  0<  filename

What that does is change the standard input of the executing script to the named file. Subsequent commands that read standard input (including the one we're interested in here, the shell built-in "read" command) will get their input from the named file. This is not the "normal" use of the exec command, by the way -- usually, you use it to mean "abandon execution of the currently running program and launch a new program".

Using the redirection command above, we can use the shell "read" command to read the file. The syntax of read is:

	
	read VARNAME1 VARNAME2 . . .

"read" takes one line from the standard input, it assigns the first word in the line to VARNAME1, the second to VARNAME2, etc. In our case we only expect there to be one word, the name of a server, on each line.

(You can't specify another source for read, which is why we redirect standard input with exec to read from a file).

Our new script, which will get the servers to connect to from a file in our home directory named "snoopyservers", looks like this:


	#!/bin/sh
	#try to get snoopy off a few file servers, they're named  in
	#the file  snoopyservers in our home directory
	SERVERLIST=${HOME}/snoopyservers
	#first, set a variable to the name of the file we want to get
	FILENAME=snoopy.zip

	if [ -f $SERVERLIST ] 
		then
		echo "File $SERVERLIST is missing"
		exit
	fi
	# don't need an "else" -- exit kills the script so it
	# won't get here if the file doesn't  exist
	
	exec 0< $SERVERLIST
	while read SERVER
	do
	  ncftp-2.3.0 ${SERVER}:/pub/pilots/${FILENAME}
	  # did we successfully get the file?
	  if [ -f $FILENAME ] 
	  then
	  # we got it! rename it and tell the user
	    mv $FILENAME  snoopy.from.${SERVER}.zip
	    echo got it from $SERVER, saved as snoopy.from.${SERVER}.zip
	  else						
	    # oops, file doesn't exist, tell the user
	    echo could not get $FILENAME from $SERVER
	  fi
done

Breaking out of a loop

You can use "break" to break out of a "while" or "for" loop. For example, if you wanted to get only one copy of snoopy.zip, you could add the "break" after the "echo got it" line:

	    mv $FILENAME  snoopy.from.${SERVER}.zip
	    echo got it from $SERVER, saved as snoopy.from.${SERVER}.zip
	    break

The final script

Below is a final revision of the script, it makes a couple small changes. First, we make the script check for several different files. Instead of "snoopy.zip", we'll search for "ls-lR", "ls-lR.Z", and "INDEX.ZIP", all of which are common names for files which give the directory listing of files on an ftp server. (The significance of the name "ls-lR" was explained in a previous issue of "Today on The World. Those files are usually stored in the /pub directory, so that's where we'll look. (You can change the script to consider any other filename as a match by adding to the string that initializes the variable CANDIDATE_NAMES.) If the script doesn't get a file with the given name, it looks for the next name on the list. However, if it does find one, it uses "break" (described above) to get out of the "for" loop, and the script continues on to the next server named in the list of servers.

# try to get some file servers
# first, set a variable to the name of the file we want to get

CANDIDATE_NAMES="ls-lR ls-lR.Z INDEX.ZIP" 

SERVERLIST=${HOME}/.lslrserverlist

for FILENAME in $CANDIDATE_NAMES
    do
    if [ -f $FILENAME ]
	then
	echo "$FILENAME already exists, terminating"
	exit
    fi
done

if [ ! -f $SERVERLIST ]
	then
	echo "$SERVERLIST doesn't exist, terminating"
	exit
fi

exec 0< $SERVERLIST

while read SERVER
    do
    for FILENAME in $CANDIDATE_NAMES
    do
    ncftp-2.3.0 ${SERVER}:/pub/${FILENAME}
	 # did we successfully get the file?
	if [ -f $FILENAME ] 
	then
	    # we got it! rename it and tell the user
	    NEWNAME=${FILENAME}.from.${SERVER}
	    mv $FILENAME  $NEWNAME
	    echo got it from $SERVER, saved as $NEWNAME
	    break  # break out of the for  loop, and go on to next server
	else						
	    # oops, file doesn't exist, tell the user
	    echo could not get $FILENAME from $SERVER
	fi
    done
done


Back to the shell scripting overview
HOME Top of Help Desk Eye On The World  The World Kiosk


             For further assistance, please send an email 
                   request to support@world.std.com

        Copyright 1996 Software Tool & Die, Inc.  All Rights Reserved.

   Software Tool & Die, Inc., 1330 Beacon St. Suite 215 Brookline, MA 02146