Shell Scripting

These are some basic notes on using shell scripting to manipulate lots of files, such as for an animation. Be aware that these apply specifically to the C shell (csh or tcsh) - things like Bourne shell or K shell often use completely different commands and variable syntax.

First, an example. For the Virtual Harlem project, I wanted to put a movie, which I had as a sequence of images, onto the stage of the Cotton Club using a flipbook XP class. So I had to create a sequence of models, each one a square with a single frame texture mapped on it. I started with a simple Inventor model (dummy.iv) which I had created and used previously; dummy.iv is a single 1x1 square with a texture map with the file name "texname". With this I could create a new pfb model with CottonClub1.sgi as its texture via these commands:

	sed s/texname/CottonClub1.sgi/ dummy.iv > temp.iv
	pfconv temp.iv CottonClub1.pfb
	rm temp.iv

To create models for all of the "CottonClub*.sgi" textures, I used a foreach loop as follows:

	foreach i (CottonClub*.sgi)
	? sed s/texname/$i/ dummy.iv > temp.iv
	? pfconv temp.iv $i:r.pfb
	? rm temp.iv
	? end

(Note: the question marks are printed by the shell, not typed by me.)

Details

Variables

Shell variables can be assigned values using the set command. e.g. "set f = foo.rgb". To use a variable's value in a command line, prefix the variable name by '$'. e.g. "sed s/texname/$f/" becomes "sed s/texname/foo.rgb/" when interpreted by the shell. Sometimes you will need to enclose the variable name in braces, so that things following it on the command line will not be interpreted as part of the variable name (or special options). e.g., "echo ${f}blah" prints "foo.rgbblah", whereas "echo $fblah" would print nothing, unless you've also assigned a value to the variable 'fblah'.

A colon immediately following a variable name is used for special modifications of the variable's value. The only specific case I really use is :r, as in the "$i:r.pfb" in the foreach example above. :r means (roughly) the root of a file name - it removes an extension from the end of a file name. So if $i is "CottonClub1.sgi", then "$i:r" becomes "CottonClub1"; the subsequent ".pfb" is then appended to the string, producing "CottonClub1.pfb". (Note that :r only removes one extension; if $i had the value "foo.rgb.bak", $i:r would be "foo.rgb".)

foreach

The foreach command is similar to a loop. It executes a series of commands once for each entry in a list; on each iteration, it assigns the next list entry as the value of the loop variable. The basic form is:

	foreach var (val1 val2 ... valN)
	  command
	  command
	  ...
	  end

The loop sets the value of "$var" to val1, executes all the commands, sets the value of "$var" to val2, executes all the commands, etc.

The parenthesised list of arguments can be any set of space-separated strings. Very often it is a set of file names, such as "CottonClub*.sgi" above, but it can in general be anything.

backquotes

Backquotes are a more advanced feature which are extremely useful in many different situations. Basically, any expression in backquotes is a command which the shell will run, and then effectively place its output in the command line in place of the backquoted expression. As a very simple example, the command date would normally print out the date and time to your terminal window; the command set x = `date` would instead assign the output of the date command to the variable x.

As a slightly fancier example, we can use backquotes to rename a bunch of files from all uppercase filenames to all lowercase. The command tr '[A-Z]' '[a-z]' will convert any uppercase letters in its input to lowercase, and print the results to its output. Combining this with echo and putting the command in backquotes, we get:
	foreach i (*.JPG)
	  mv $i `echo $i | tr '[A-Z]' '[a-z]'`
	end

script files

Shell script commands can either be simply typed in at the shell command line, or they can be put into a script file and the file then run. A script file is useful in case you want to re-use the commands, or aren't sure that they'll work correctly the first time. To create a script file, simply type the commands into a file the same as you would type them at the command line. You can then run the file by csh script-file. If you want to run it like an executable, add the line

	#!/bin/csh -f

as the very first line of the script, and make the file executable (chmod +x script-file). Then you can just type script-file.

You can also pass command line arguments to a script, just like a normal program. The arguments are assigned to the shell variable 'argv', and can be accessed as '$argv[1]', '$argv[2]', or more simply as '$1', '$2', etc. For example, this script will pfconv its argument to a pfb file:

	#!/bin/csh -f
	pfconv $1 $1:r.pfb

The special variable '$*' represents all of the command line arguments. I often use this in scripts which run CAVE programs, in order to pass any command line arguments on to the CAVE program itself. For example:

	#!/bin/csh -f

	setenv DATA_DIR ./DATA
	setenv PFPATH ${DATA_DIR}:${DATA_DIR}/Textures:scenes

	./myxp all.all $*

Further Examples

Having created the Cotton Club movie models, I needed to put them all in a scene file in order to place them under a flipbook. Another quick foreach, followed by some minor editing with vi, accomplished this:

	foreach i (CottonClub*.pfb)
	  echo "	object (file=" $i ")" >> Scene
	end

(Note the use of >> to append the new entries to an existing scene file.)

In my bin directory I have a few very simple utility programs that I wrote for dealing with large collections of animation frames in scripts. These include count, which prints a range of numbers; countp, which prints zero-padded numbers; and zeropad which takes a number and prints it out with leading zeroes so that it's 4 characters long (or however many characters you request). One use of these programs is to take a bunch of frames that a renderer may have annoyingly saved as "frame1.rgb", "frame2.rgb", ..., "frame100.rgb", and rename them to "frame0001.rgb", ..., "frame0100.rgb":

	foreach i (`count 1 100`)
	  mv frame$i.rgb frame`zeropad $i`.rgb
	end


Last modified 5 April 2000.
Dave Pape, pape@evl.uic.edu