2.16. Generalizing Your Code

All programs and scripts require input in order to be useful.

Inputs commonly include things like scalar parameters to use in equations and the names of files containing more extensive data such as a matrix or a database.

2.16.1. Hard-coding: Failure to Generalize

All too often, inexperienced programmers provide what should be input to a program by hard-coding values and file names into their programs and scripts:

#!/bin/csh

# Hard-coded values 1000 and output.txt
calcpi 1000 > output.txt
            

Many programmers will then make another copy of the program or script with different constants or file names in order to do a different run. The problem with this approach should be obvious. It creates a mess of many nearly identical programs or scripts, all of which have to be maintained together. If a bug is found in one of them, then all of them have to be checked and corrected for the same error.

2.16.2. Generalizing with User Input

A better approach is to take these values as input:

#!/bin/csh

printf "How many iterations? "
set iterations = "$<"
printf "Output file? "
set output_file = "$<"

calcpi $iterations > $output_file
            

If you don't want to type in the values every time you run the script, you can put them in a separate input file, such as "input-1000.txt" and use redirection:

shell-prompt: cat input-1000.txt
1000
output-1000.txt
shell-prompt: calcpi-script < input-1000.txt
            

This way, if you have 50 different inputs to try, you have 50 input files and only one script to maintain instead of 50 scripts.

2.16.3. Generalizing with Command-line Arguments

Another approach is to design the script so that it can take command-line arguments, like most Unix commands. Using command-line arguments is quite simple in most scripting and programming languages.

In all Unix shell scripts, the first argument is denoted by the special variable $1, the second by $2, and so on.

$0 refers to the name of the command as it was invoked.

Bourne Shell Family

In Bourne Shell family shells, we can find out how many command-line arguments were given by examining the special shell variable "$#". This is most often used to verify that the script was invoked with the correct number of arguments.

#!/bin/sh

# If invoked incorrectly, tell the user the correct way
if [ $# != 2 ]; then
    printf "Usage: $0 iterations output-file\n"
    exit 1
fi

# Assign to named variables for readability
iterations=$1
output_file="$2"    # File name may contain white space!

calcpi $iterations > "$output_file"
                
shell-prompt: calcpi-script
Usage: calcpi-script iterations output-file
shell-prompt: calcpi-script 1000 output-1000.txt
shell-prompt: cat output-1000.txt
3.141723494
                

C shell Family

In C shell family shells, we can find out how many command-line arguments were given by examining the special shell variable "$#argv".

#!/bin/csh

# If invoked incorrectly, tell the user the correct way
if ( $#argv != 2 ) then
    printf "Usage: $0 iterations output-file\n"
    exit 1
endif

# Assign to named variables for readability
set iterations=$1
set output_file="$2"    # File name may contain white space!

calcpi $iterations > "$output_file"
                
shell-prompt: calcpi-script
Usage: calcpi-script iterations output-file
shell-prompt: calcpi-script 1000 output-1000.txt
shell-prompt: cat output-1000.txt
3.141723494
                

2.16.4. Self-test

  1. Modify the following shell script so that it takes the file name of the dictionary and the sample word as user input instead of hard-coding it. You may use any shell you choose.

    #!/bin/sh
    
    if fgrep 'abacus' /usr/share/dict/words; then
        printf 'abacus is a real word.\n'
    else
        printf 'abacus is not a real word.\n'
    fi
    
    
  2. Repeat the above exercise, but use command-line arguments instead of user input.