Cover V12, I09

Article

sep2003.tar

I "new" It -- A Generator for New Shell Script Templates

Joseph Pietras

I knew it! Just when everything seems to be going okay, something else breaks. It seems like the life of a Unix systems administrator, when not rebuilding computers, fixing the network, and putting out fires, is spent writing scripts to make something work. In this article, I'll describe "new", the script I use to generate a script template.

Experience has shown that a large portion of script writing is repetitive. Minimally, a shell needs to read the command-line options do the work and exit cleanly. Consequently, I find that I write the "template" part over and over. And I have to maintain multiple templates because companies vary in the kind of scripts that they use. Most of the companies for which I consult prefer scripts to be written in Bourne shell (/bin/sh) since it is (in theory) more portable. However, I prefer to write in Korn shell, because it has so many nice language extensions, not to mention the speed improvement. And then there's bash -- lots of companies are using it more and more because of Linux. Of course, some sys admins require C-shell (or TC-shell) scripts.

So, naturally, I evolved my templates for writing scripts into a script. I wrote a shell script that writes a shell script. I guess it's the academic in me: I love recursion. My script, "new", outputs any one of the five templates I was maintaining -- the first versions of "new" simply used /bin/cat, but I couldn't leave it at that. After many iterations, I had a script that was well over 2000 lines.

Eventually, I had to break up the script because, well, the script generator, "new", would generate a script that, when run, would generate a script that, when run, would generate a script, that... Anyway, I finally divided "new" into three scripts named "new", "workctl", and "working". Now it's much easier to explain. In this article, I will describe "new", and explain how to use it. I will also detail the "workctl" and "working" scripts.

The "new" script will generate a template script using any of these shells:

  • Bourne shell (/bin/sh)
  • Korn shell (/bin/ksh)
  • Bash shell (/bin/bash)
  • C-shell (/bin/csh)
  • TC-shell (/bin/tcsh)

Regardless of the shell, the template script performs these steps:

1. Creates a variable NAME and dynamically binds it with the name of the template script. If the name of the template script is changed, the NAME variable automatically reflects the change.

2. Examines the environment variable ECHO. If the ${ECHO} is set to any of the values true/yes/on (case insensitive), then the script turns on debugging with "set -x" (set echo in the C-shell and TC-shell).

3. Creates a USAGE variable for I/O from the template shell. When you run the template shell with zero command-line arguments, the USAGE variable is printed to the screen.

4. Creates a set of uniquely named temporary files to be used by the template as needed. These can be used to hold sed scripts, awk scripts, temporary edits, etc.

5. Creates the template shell code needed to clean up temporary files under common circumstances (i.e., when the template shell receives an interrupt (SIGINT), hangup (SIGHUP), or terminate (SIGTERM) signal). These can be easily extended. For C-shell and TC-shell, an "onintr" logic is put into the template to handle the temporary file cleanup chores.

6. Generates code so that the template shell will accept the "-" option or the "--" option to override usage, thereby allowing the programmer of the template shell to have options that enable the user of the template shell to "take all the defaults" of the template shell.

7. Prompts for options that the template shell program will accept.

The "new" script can create a template script with either "getopts" or "gnu" style command-line parser logic. Alternatively, "new" can also be used to create a template script that does not have command-line parser logic. So, there are essentially three ways to use "new" to create a new template script:

  • With "gnu" style command-line parsing logic
  • With "getopts" style command-line parsing logic
  • Without command-line parsing logic

Basically, the difference between "getopts" and "gnu" style parsing is that the traditional Unix "getopts" style only allows options to be one character long, while the (so-called) "gnu" style parsing allows each one-character command-line option have a "long format" counterpart. The long format options are often easier to remember and are very useful when used within scripts. Even if you leave a script for a year or more, when you come back to editing it again, you will remember what the commands in the script are doing because of the long format options.

To make use of this feature, GNU style parsing logic is used by "new". Consequently, users of "new" can specify command-line options in either long format or short format.

These are the options that control the choice of parser logic that "new" outputs to the template script:

--gnustyle | -g Enable gnu style options
--nognustyle | -n Disable gnu style options
--getopts | -G Enable getopts style options
--nogetopts | -N Disable getopts style options
--omit | -o Alias for --nognustyle and --nogetopts

When --omit has been chosen, the script writer is on his own and will have to edit the template and write any command-line parsing desired, otherwise "new" will prompt the user to enter the options that the template script should recognize. Prompting is terminated by entering an empty line or EOF (usually CTRL-D).

If "gnu" style is in effect, then "new" prompts the user twice -- once for the long format and once for the short format of the option. The short format defaults to the first character of the long format, just as you would expect.

Now, when "gnu" style options are used there is a subtle issue with regard to white space and the short format version of an option. Here is what I mean -- if the option --output=file is valid and -o is the short format version of this option, then -ofile is valid. But, should -o file also be valid?

Some programmers argue that it should, and others argue that it shouldn't. It's hard to please the customer with this kind of thing going on. So, I solved the problem with more software. That's why the "new" script has these command-line options:

--threevariants | -3
--twovariants   | -2
The template created by "new" will or won't accept the -o file variant of the "gnu" style short format version, depending on which of the above options are in effect. Other options to "new" are:

--plugin        | -P
--noplugin      | -I
These options control whether the template script will output "SMDP_*" variables. They are used only if the script template being generated is going to be used with OpenSysMon (OSM) Meta Monitoring Software. If you are interested in plug-ins for OSM, check out the Web site (http://www.opensysmon.com).

The --date(-d) and --nodate(-D) options control whether "new" puts a date stamp into the template that is output.

Previously, I mentioned that "new" is one of three scripts in this set -- the other two being "workctl" and "working".

These two companion scripts exist to facilitate solving the long response time problem. Shell script software typically runs fast, however some shell scripts can be quite complicated and require a very long execution time. And, when they do, they can leave the user hanging. Any shell script that will have a long response time can employ the "working" shell script to overcome this long response time problem.

To effectively control the "working" script from any other script (including script languages that do not implement functions and traps), the programmer can use "workctl". So, depending upon the programmer's choice of a scripting language "workctl" may or may not be needed. However, from within "new", regardless of the kind of template script being generated, I consistently output calls to "workctl". I do this because it makes "new" easier to write and makes the the generated template easier to read.

The template generated by "new" can automatically call "workctl", which calls "working", in order to produce a template script that is working as soon as it is created. By that, I mean it works immediately and controls the "working" script in such a way that you, the programmer, see what the "working" script is doing.

Options that control these scripts are:

--work | -w Enables workctl in the template
--nowork | -W Disables workctrl

For new users of "new", it is recommended that "--work" be used. This makes the whole process easier and facilitates learning how to use "new". Naturally, you will have to download the "working" software also.

For example, enter these commands to build a working, advanced script:

#edinburghBASH: ./new --work --korn --omit > template
#edinburghBASH: chmod +x ./template
#edinburghBASH: ./template -
This example assumes that you already have "workctl" and "working" in your PATH. It also introduces the last two sets of options I will discuss. The type of shell created by "new" is controlled by these options:

--bourne | -B Produce a /bin/sh template
--korn | -k Produce a /bin/ksh template
--bash | -b Produce a /bin/bash template
--csh | -c Produce a /bin/csh template
--tcsh | -t Produce a /bin/tcsh template
--shell | -s Shell options are aliases for --bourne and -B

The last options have to do with controlling where the output of "new" goes. As this example shows, when a script template is generated with "new", the template will be sent to standard output, which typically is redirected. Or, if the command-line option --output=<file> is given, the template is written to <file>.

Installation of "new" (as well as "workctl" and "working") is trivial; you simply put the scripts in your PATH. Otherwise, they are fully functional and do not require any configuration. However, if you want to change any of the defaults for the scripts, you can edit the configuration section and simply toggle the default from TRUE to FALSE. The "new" script will then automatically adapt itself, including its usage line, to the new defaults.

One caveat -- all three of these scripts must know the numeric values of SIGUSR1 and SIGUSR2. Additionally, "new" needs to know the values of SIGHUP, SIGINT, and SIGTERM. All of these numeric values can be obtained with kill -l, or you can look in /usr/include/sys/signal.h.

The "new" software is available for download from the Sys Admin Web site at: http://www.sysadminmag.com or from ftp://ftp.opensysmon.com/pub/new/ directory. There is a zip file along with the individual files. Note that http://www.opensysmon.com is a shell script archive site that also contains an open source system monitoring and network monitoring software package. Many platforms are supplied already compiled.

Joseph Pietras is a Sr. UNIX systems programmer living and working in sunny San Diego, California. He holds a MS in Computer Science and loves all things UNIX! When he's not writing code or at the beach riding the waves he can be reached at jpietras@ChironComputing.Com.