next up previous
Next: A Word on HTML Up: intro_Unix Previous: sed

Shell Scripting

Shell scripting is a lot about trial and error. The most frustrating aspect can be sorting out the correct syntax for double quotes, single quotes, and reverse single quotes! The following examples highlight some of the most common features used in typical csh scripts:

    #!/bin/csh
    # rename -- simple script to rename .txt files to .dat
    #
    if ($#argv < 1) then
        echo "Usage: $0 txt_file [ txt_file ... ]"
        exit 1
    endif
    #
    foreach txt_file ($argv)
        echo $txt_file
        set dat_file = `echo $txt_file | sed s/.txt/.dat/`
        mv -f $txt_file $dat_file
    end

The first line indicates to the shell that we want to use csh (you can actually specify any command that accepts scripts here, e.g. /local/bin/bash, /local/bin/perl, etc.). After the first line anything following a # is ignored as a comment. We next check to see that at least one argument was passed (the $#var construct returns the number of elements in a multi-valued shell variable var). The double quotes "" are necessary in the echo statement to avoid misinterpretation by the shell of the square brackets [ ] while at the same time allowing the shell to expand $0, which stands for the name of the script itself (confusing, isn't it?). Crudely, double quotes allow for some interpretation by the shell of special characters on an input line, single quotes allow for none, while no quotes allow full interpretation.

A return code of 1 is used to indicate an error and can be tested by immediately checking the value of the special shell variable status after running the script.

The main procedure is to loop over all the arguments and use sed together with mv to change the extensions of the files. The intermediate variable dat_file is unnecessary but helps to make the procedure clearer. Note the use of the reverse single quotes which causes the result of the enclosed command, in this case a pipe of the name of the .txt file to sed, to be returned as a value.

To use this script like a normal command, you need to chmod it to executable status, e.g. chmod a+x rename. Then type rename to run it (or ./rename if `.' is not in your path).

Here's another example:

    #!/bin/csh
    # examine -- simple script to graphically show file size
    #
    if ($#argv < 1) then
        echo "Usage: $0 file [ file ... ]"
        exit 1
    endif
    #
    foreach file ($argv)
        echo -n $file': '
        @ nl = `wc $file | awk '{print $1}'`
        while ($nl > 0)
            echo -n '*'
            @ nl--
        end
        echo ''
    end

The main loop here uses wc together with awk (which we use to extract the first column of the output from wc) to get the number of lines of each file (recall we could write @ nl = `awk 'END {print NR}' $file` instead). This value is then used in a decremental loop to print one asterix for each line in the file. The -n argument to echo suppresses the linefeed; the echo '' command forces one. Note the use of @ for shorthand integer variable manipulation and arithmetic (set nl = $nl - 1 would also work but it's not as clean).

Hint: You can get the shell to echo each line of your csh script before it's executed by adding a -x argument at the end of the shell declaration line (e.g. #!/bin/csh -x). This is very useful for debugging...

As a final example here's a script to run a code three times with three different inputs that are specified at the beginning of the script (this is done to make the values easy to change later on; alternatively these values could be passed to the scripts via argv):

    #!/bin/csh
    set run_values = (10 100 1000) # specify run values here
    if ($#argv != 0) then
        echo $0 takes no arguments
        exit 1
    endif
    #
    foreach value ($run_values)
        echo Starting run with value = $value
        ./myprog $value >&! log$value
        if ($status) echo WARNING: Problem during execution
    end

Notice that a shell variable can be set to an array using the ( ) notation. To access elements of an array, use [ ]: $run_value[2] has a value of 100 in this example. WARNING: array indices start at 1, not 0, in csh. Why this is different from C (which starts at 0) is one of those unanswered mysteries of life.

The call to myprog simply passes the index value $value as an argument. It's up to myprog to use the argc and argv formalism in main() to read the passed value (assuming myprog is written in C/C++ of course). Output (including stderr) is redirected forcefully to a log file that will be different for each run. The status variable is checked to see if there was a problem during the run (an example of how religiously returning meaningful values from your codes can be useful).


next up previous
Next: A Word on HTML Up: intro_Unix Previous: sed
Massimo Ricotti 2009-01-26