Chapter 11. Shell Scripting

For the majority of this book, we’ve looked at the various elements that make up bash and how you can use them in writing shell scripts. If you’ve used other programming languages you will know that there is a difference between writing a piece of code that gets a job done and writing a piece of code that does the job but is also maintainable and conforms to what we could call “good practice.”

This chapter will give a brief introduction to some aspects of good practice and writing maintainable shell scripts along with helpful tips and tricks that you can use to make writing scripts easier.

What’s That Do?

Six months ago you coded up a 100 line shell script. It made perfect sense then, but now you look at it and wonder, “Just what does that do?” This is a common pitfall among programmers—especially those writing in a shell language. Unfortunately, shells have developed with more than their fair share of obscure punctuation. This is a blessing for keeping typing to a minimum but doesn’t help readability. It’s important to make your code as readable as possible.

Comments

The first rule of shell scripting is to comment your code. You should do this right from the start, even if the script is only a couple of lines long. Shell scripts have a habit of growing from a couple of lines to many hundreds of lines as more features are added, so it’s best to get into the habit of commenting your code right at the beginning.

To start with, consider having a main header or banner for your scripts. The information in the header should, at a minimum, say what the script does. Here is an example of a script header:

#!/bin/bash
#####################################################
# Name: graphconv.sh
#
# Converts graphics files from one format to another.
#
# Usage: graphconv.sh <input-file> <output-file>
#
# Author: C. Newham
# Date: 2004/12/02
#####################################################

This main header gives the name of the script, a brief summary of what it does, usage information, the name of the author, and when the script was written.

If you are using a source control system (e.g., CVS), you can dispense with the author and date as these will be stored when the script is archived. If you aren’t using such a system, we strongly advise that you not only include the above information but also place in the header additional data such as modification dates and authors.

Whatever system you use, make sure that you make the format of the banner a standard across all of your scripts.

Every function should also have a header. If it is a standalone function, it should have a main header, as given above. If it is a function used locally in a script, it should have a simpler banner stating what it does, what parameters it expects, and what it returns, e.g.:

# Changes the filename extension
#
# param: $infile - the original filename
#
# returns: the modified name with new extension.
#
function change_filename( )
...

Comments should also be used frequently in your code to say what the code is doing. While we aren’t about to dictate style, comments within the flow of the code are generally better on a line by themselves, while variable declaration comments are better on the same line as the variable:

startup_dir=/home/startup/  # directory with startup files
file_limit=50               # maximum number of files to process
...
if [ -d "$startup_dir" ]
then
    # the startup directory exists so read any initialisation file.
    echo "initialising file processing..."

Variables and Constants

Headers and comments are just one way to document your code. Another is by the use of descriptive variable names. Good variable names should give an indication of what the variable represents. Names like “x”, “resn” or “procd” will only have meaning at the time that you write the script. Six months down the track and they will be a mystery.

Good names should be short but descriptive. The three examples above might have been more meaningfully written as “file_limit”, “resolution”, and “was_processed”. Don’t make the names too long; the name “horizontal_resolution_of_the_picture” just clutters a script and takes away any advantage in making the name so descriptive.

Constants should be in uppercase and should normally be declared as read-only:

declare -r CAPITAL_OF_ENGLAND="London"

You should always avoid “magic numbers” sprinkled throughout the code by using constants. For example:

if [[ $process_result == 68 ]]
...

should be replaced with:

declare -ir STAGE_3_FAILURE=68
...
if [[ $process_result == $STAGE_3_FAILURE ]]
...

Not only does this make the code more readable but it makes changing the value easier, especially if it is used numerous times in the script.

Starting Up

In Chapter 6 we talked about using getopts to obtain options and arguments passed in to a shell script. This command makes it easy for the script programmer to process what the user has provided, but what about the other half of the deal? The programmer must make an effort to make life as easy for the user as possible. Nothing makes a user more irate than a script that doesn’t take standard arguments, doesn’t provide a usage message, doesn’t process the arguments in the expected way, and forces the user into a way of thinking that the programmer thinks is the right way. Having to examine the source code for a script to find out what is an acceptable argument or option is usually the last straw!

The Free Software Foundation has published a set of guidelines for writing GNU software that suggests standard ways in which UNIX utilities should operate.[1] When writing your own shell scripts, it is worthwhile to follow the guidelines because your script will then look familiar to users who have used other command-line programs.

At a minimum your script should provide single letter options (such as -h) and long options with the double dash (such as —help). It should also provide two options: —help and —version. From the GNU manual:

—version

This option should direct the program to print information about its name, version, origin, and legal status, all on standard output, and then exit successfully. Other options and arguments should be ignored once this is seen, and the program should not perform its normal function.

—help

This option should output brief documentation for how to invoke the program, on standard output, then exit successfully. Other options and arguments should be ignored once this is seen, and the program should not perform its normal function.

Near the end of the —help option’s output there should be a line that says where to mail bug reports. It should have this format:

  • Report bugs to mailing-address.

Table 11-1 lists a few of the common single-letter and long options that you may consider using for your own scripts. This list is by no means exhaustive and is intended merely for guidance.

Table 11-1. Possible options

Long option

Option

Examples where used

—all

-a

du, ls, nm, stty, uname, unexpand

—append

-a

etags, tee, time

—binary

-b

cpio, diff

—blocks

-b

head, tail

—date

-d

touch

—directory

-d

cpio

—exclude-from

-X

tar

—file

-f

fgrep

—help

-h

man

—long

-l

ls

—line

-l

wc

—links

-L

cpio, ls

—output

-o

cc, sort

—quiet

-q

who

—recursive

-r

rm

—recursive

-R

ls

—silent

-s

Synonym for -quiet

—unique

-u

sort

—verbose

-v

cpio, tar

—width

-w

pr, sdiff

For commands that take one or more input files and produce an output file it is considered good practice to make only the input files normal arguments (i.e., command filename ) and have the output file specified by an option (i.e., command -o filename ).

Another thing to watch out for is assuming that a particular environment variable needed by your script has been set in the users’ environment. If your script is relying on the user to have set an environment variable, it is probably better to redesign your script to allow the value to be passed in as an argument.

Potential Problems

Here are some useful things to watch out for when writing shell scripts. Being aware of them will not only save you time in tracking down bugs but will also make your scripts more robust, more readable, and above all, more maintainable.

  • Don’t create massive scripts or functions that try to do everything. Split functionality up into smaller units and place them in functions. This not only makes the code easier to read but makes it easier to debug.

  • Always place the shell execution directive (e.g., #!/bin/bash) at the top of your scripts to ensure they will be run by bash.

  • Don’t use reserved words for variable names. This can become very confusing:

    let let="echo"
    let echo="hello"
    echo "$echo world"
  • Be careful with whitespace. Attempting the following assignment will not give the expected result:

    cat = 5
  • Don’t use the same names for variables and functions:

    function letter
    {
         echo $1etter
    }
    
    letter=letter
    letter letter

    This causes more confusion that it’s worth. While this example is contrived, be on your guard for more subtle examples. To guard against this, try and name your functions using verbs, e.g., function print_letter.

  • Be careful when using the test operator [...]. The following two if statements are not the same, although they look very similar:

             if [ "$var" = 42 ]
             if [ "$var" -eq 42 ]

    The first is a string comparison, the second an integer comparison. We suggest using ((...)) for arithmetic comparisons in if statements.

Don’t Use bash

Sometimes you might start writing a script and after several hours of work find that you’ve created a monster with many hundreds of lines of complicated code. This is not always a bad thing, but it is a good idea to always be thinking about whether the job could be done in a better way.

Usually the choice of programming language should take place at the design stage. If you are starting from scratch on a Unix system you will have many options, including C and C++, perl, python, and a host of others. They all have their advantages and disadvantages, and no one language will be the best solution for every problem.

If you find that your script has a huge amount of processing to do quickly or if the script requires mathematical capabilities beyond simple integer arithmetic, it might be worthwhile considering C or C++ for the job. If you are looking for better portability across systems, python or perl might be a better match to the task.

However, even if bash is not suitable in the final solution to a problem, you might find it makes an excellent language for mocking up your solution and trying out various options.



[1] The document is available at http://www.gnu.org/prep/standards/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.162.247