A.0.0 Miscellaneous Topics

The scope of this book is to look at as many compact examples of low level system programming in multiple languages in a fashion that is both usable and simple. In addition to the introductory material and examples there are some things that can be examined outside of the context of the book in appendices - just some side notes of interest I have collected and don't really fit within the scope of the core material.

A.0.1 Options Parsing

Options parsing can be difficult at times; to say the least. There exist a number of common methods and libraries to assist with options parsing. In this text, a look at writing option and argument parsing homespun and with a little help. Simple Parsing in sh

Simple parsing is easy in the shell:

        while [ "$#" -gt "0" ]
        do
                case $1 in
                        -F)
                                F_FLAG=1
                                ;;
                        -f)
                                shift
                                FILE_ARGUMENET=$1
                                ;;
                        -u)
                                Usage
                                exit 0
                                ;;
                        *)
                                echo "Syntax Error"
                                Usage
                                exit 1
                                ;;
                esac
                shift
        done

Above, the input string is iterated over and particular options act or assign a variable. The posix getopt capability allows for built in - parsing:

        while getopts ":f:Fu" opt; do
                case $opt in
                        F) F_FLAG=1;;
                        f) FILE_ARGUMENT=$OPTARG;;
                        u) usage;;
                        *) usage
                                exit 1
                                ;;
                esac
                shift
        done

A colon after an option indicates it requires an argument. The getopt code is far more compact than the first example. What if the script requires long options? One approach is simply to hard code long options:

        while [ "$#" -gt "0" ]
        do
            case $1 in
                -F|--setflag)
                    F_FLAG=1
                    ;;
                -f|--file)
                    shift
                    FILE_ARGUMENET=$1
                    ;;
                -u|--usage)
                    Usage
                    exit 0
                    ;;
                *)
                    echo "Syntax Error"
                    Usage
                    exit 1
                    ;;
            esac
            shift
        done

Setting up long options appears to be simple, however, it can quickly get out of control using the method show above. Instead, writing code to handle long options that can either be sourced in or easily dropped into scripts makes far more sense. Grigoriy Strokin has a good script that can either be copied in or sourced and can be found on his website. Following is the same code from above using getoptex:

        . getoptx.sh
        while getoptex "F; f; u. setflag file usage." "$@"; do
                F) F_FLAG=1;;
                f) FILE_ARGUMENT=$OPTARG;;
                u) usage;;
                *) usage
                        exit 1
                        ;;
        done

It is pretty obvious that the single character is mapped to the the long option past the first . and the full terminator is the second dot. Of course, there is an even easier method as long as a few rules are observed:

        while [ "$#" -gt "0" ]
        do
                opt="${1//-}"
                opt=$(echo "${opt}" | cut -c 1 2>/dev/null)
                case $opt in
                        F) F_FLAG=1;;
                        f) shift;FILE_ARGUMENT=$1;;
                        u) usage;;
                        *) usage; exit 1;;
                esac
                shift
        done

The problem with the last method is the long options are not hard-coded, the first character of the alpha string is cut and used as an option. In other words, --help and --heck will do the same thing. The idea is harmless except no options can be mixed and matched. Generally speaking, not having a --help and --heck valid in the same script or program should be avoided if possible. Options in Perl

With no case statement built in, doing options parsing in Perl can be a little tricky. Using the same example from the shell code above a simple options parser might look like: [ 1 ]

        while ( my $arg = shift @ARGV ) {
            if ( $arg eq '-F' ) {
              $F_FLAG = 1;
            } elsif ( $arg eq '-f' ) {
              $FILE_ARGUMENT = shift @ARGV;
            } elsif ( $arg eq '-u' ) {
              usage();
            } else {
              usage();
              exit 1;
            }
         }

Relative to the shell, Perl seems a bit heavy handed in the amount of work needed. In Perl the options for handling are almost limitless. Associative arrays, hashes, arrays or just plain scalars arranged a certain way could be used.

Of course, another great thing about Perl is how simplistic string operations are handled. Using a method similar to the last shell method above can simplify the code a great deal:

        for (my $argc = 0; $argc <= @ARGV; $argc++) {
                $opt = $ARGV[$argc];
                $opt =~ s/--//; # Get rid of 2 dashes
                $opt =~ s/-//; # Get rid of 1 dash
                $opt =  substr($opt,0,1); # cut the first char
                if ($opt eq 'F') {
                        $F_FLAG=1;
                } elsif ($opt eq 'f') {
                        $FILE_ARGUMENT=$ARGV[++$argc];
                } elsif ($opt eq 'u') {
                        usage();
                } else {
                        usage();
                        exit 1;
                }
        }

Of course, the same two problems from the shell-code which cuts out the first alphanumeric exists; no two long options can start with the same letter and there is no verification of long options. Not unlike the shell, a simple list can be used to verify that long options are valid, following is an example sub routine:

        ...
        my @valid_optlongs=("setflag", "file", "usage");
        my @valid_optshort=("F",       "f",    "u");
        ...
        sub parseopt{
                my ($opt) = shift;

                $opt =~ s/--//; # Get rid of 2 dashes
                $opt =~ s/-//; # Get rid of 1 dash

                if (scalar($opt) > 1) { 
                        for ($i = 0; $i < @valid_optlongs; $i++) {
                                if ($opt eq $valid_optlongs[$i]) {
                                        return $valid_optshort[$i];
                                }
                        }
                } else {
                        return $opt;
                }
        }

Essentially instead of just trimming out the first valid alphanumeric, if the option is a long option check it against the list of valid long options and return the matching single byte option the long option correlates to.

Ultimately, using the getopt module should be done if it is available, why reinvent the wheel? Here is an example of using the Getopt module:

use Getopt::Std;

getopt ('f:uF');

        die "Usage: $0 [ -f filename -u ]\n"
                unless ( $opt_f or $opt_u );

        if ($opt_f) {
                my $filename = shift @ARGV;
        } elsif ($opt_u) {
                usage();
                exit 0;
        }

Definitely shorter and compact.

The oldest high level programming language - not unlike Perl - has many different approaches a programmer can take without using libraries:

        int main(argc, argv)
            int argc;
            char    *argv[];
        {
            if (argc < 2) {
                printf("usage: %s number-of-execs sbrk-size job-name\n",
                    argv[0]);
                exit(1);
            }
        ....
        int main (argc, argv) {
                for (c = 0; c <=argc; c++) {
                        if (argc[c] == 'F') {
                                F_FLAG=1
        ...

libc offers up two levels of built in options handling, one for single options and one for long options. Since the options handling routines are in modern implementations, the examples will use GNU's version.

        ...
        #include <getopt.h>
        ...
        int main (int argc, char **argv)
        {
        int c;
        char * file;

        while ((c = getopt(argc, argv, "F:f:u:")) != -1) {
                switch (c) {
                case 'F':
                        F_FLAG=1
                        break;
                case 'f':
                        file = optarg;
                        break;
                case 'u':
                        usage();
                        return 0;
                        break;
                default:
                        usage();
                        return 1;
                        break;
                }
        }

Far more succinct than what may have happened using the previous C examples which would have been pretty spaghetti'd. Long options are even more interesting. The GNU C library internally handles assignment of long options by using the single alpha as the key inside of a data structure:

        ...
        #include <getopt.>
        ...
        int main(int argc, char **argv)
           while (1)
              {
                static struct option long_options[] =
                  {
                    {"setflag", no_argument,       0,  'F' },
                    {"file",   required_argument,  0,  'f' },
                    {"usage",  no_argument,        0,  'u' },
                    {0,0,0,0} /* This is a filler for -1 */
                  };

                int option_index = 0;

                 c = getopt_long (argc, argv, "F:f:u:", long_options, &option_index);

                if (c == -1) break;

                switch (c) {
                case 'F':
                        F_FLAG=1;
                        break;
                case 'f':
                        file = optarg;
                        break;
                case 'u':
                        usage();
                        return 0;
                        break;
                default:
                        usage();
                        return 1;
                        break;
                }
        }

Short, sweet and to the point.
A.0.2 Using CPAN


Although the C libraries are quite extensive depending upon the system they can be difficult to track down and maintain. Perl on the other hand has a great deal of non core libraries and modules that can easily be downloaded and installed from the Comprehensive Perl Archive Network or CPAN for short. A much simpler method, however, is to use the CPAN shell which is provided with a base Perl install.

The cpan shell keeps the preferences for connections in $HOME/.cpan. In order to use it, the shell must be initialized. Following are some of the example questions that come up - the long explanation that the cpan shell prints out have been omitted:

        You don't seem to have a user configuration (MyConfig.pm) yet.
        Do you want to create a user configuration now? (Y/n) [yes] 
        Would you like me to configure as much as possible automatically? [yes] No
        CPAN build and cache directory? [/home/jrf/.cpan]
        Download target directory? [/home/jrf/.cpan/sources]
        Directory where the build process takes place? [/home/jrf/.cpan/build]
        Store and re-use state information about distributions between
        CPAN.pm sessions? [yes] 
         <prefs_dir>
        Directory where to store default options/environment/dialogs for
        building modules that need some customization? [/root/.cpan/prefs] 
         <auto_commit>
        Always commit changes to config variables to disk? [no] Yes
         <build_cache>
        Cache size for build directory (in MB)? [100] 
         <index_expire>
        Let the index expire after how many days? [1] 
         <scan_cache>
        Perform cache scanning (atstart or never)? [atstart] 
         <cache_metadata>
        Cache metadata (yes/no)? [yes] 
         <use_sqlite>
        Use CPAN::SQLite if available? (yes/no)? [no] 
         <prerequisites_policy>
        Policy on building prerequisites (follow, ask or ignore)? [ask] 
         <build_requires_install_policy>
        Policy on installing 'build_requires' modules (yes, no, ask/yes,
        ask/no)? [ask/yes] no
         <check_sigs>
        Always try to check and verify signatures if a SIGNATURE file is in
        the package and Module::Signature is installed (yes/no)? [no] yes
         <test_report>
        Email test reports if CPAN::Reporter is installed (yes/no)? [no] 
        ...

Most of the questions can
use the default, but do read them all to be sure. Of most interest is the selection of download servers:

ftp:, or http: -- that host a CPAN mirror.

        (1) Africa
        (2) Asia
        (3) Central America
        (4) Europe
        (5) North America
        (6) Oceania
        (7) South America
        Select your continent (or several nearby continents) [] 5
        (1) Bahamas
        (2) Canada
        (3) Mexico
        (4) United States
        Select your country (or several nearby countries) [] 4
        (1) ftp://cpan-du.viaverio.com/pub/CPAN/
        (2) ftp://cpan-sj.viaverio.com/pub/CPAN/
        (3) ftp://cpan.cs.utah.edu/pub/CPAN/
        (4) ftp://cpan.erlbaum.net/CPAN/
        (5) ftp://cpan.hexten.net/
        (6) ftp://cpan.llarian.net/pub/CPAN/
        (7) ftp://cpan.mirrors.tds.net/pub/CPAN
        (8) ftp://cpan.netnitco.net/pub/mirrors/CPAN/
        (9) ftp://cpan.pair.com/pub/CPAN/
        (10) ftp://ftp-mirror.internap.com/pub/CPAN/
        (11) ftp://ftp.ccs.neu.edu/net/mirrors/ftp.funet.fi/pub/languages/perl/CPAN/
        (12) ftp://ftp.dc.aleron.net/pub/CPAN/
        (13) ftp://ftp.epix.net/pub/languages/perl/
        (14) ftp://ftp.ncsu.edu/pub/mirror/CPAN/
        (15) ftp://ftp.ndlug.nd.edu/pub/perl/
        (16) ftp://ftp.osuosl.org/pub/CPAN/
        34 more items, hit RETURN to show them
        Select as many URLs as you like (by number),
        put them on one line, separated by blanks, hyphenated ranges allowed

Generally I almost always pick the same ones I have used before:

        Select as many URLs as you like (by number),
        put them on one line, separated by blanks, hyphenated ranges allowed
         e.g. '1 4 5' or '7 1-4 8' [] 3 9 10 

A good litmus test is to update the CPAN bundle like so:

                #
                # install Bundle::CPAN
                #

Depending upon which modules are already installed it may need to install dependancies. Note the syntax, the :: can also do multiple levels:

                #
                # install Net::FTP
                #

When not doing a full bundle, the Bundle_Name::Module is the form. CPAN can also be searched from the shell:

                #
                # i/NAME/
                #

The cpan shell can make a system programmer's life a lot easier.

A.0.3 The Basics of Make

The make utility is used to manage program build and installation. Make uses a Makefile which has directives stored in it. It also supports a wide variety of macros and other capabilities. This text will only examine the very basics of a Makefile.

In the simplest form a Makefile for the hello_world program might look like:

        # Makefile
        CC=/usr/bin/cc
        all:
                ${CC} hello_world.c -o hello_world

Now type:

                #
                # make 
                #

Which defaults to the all target.
Make variables start at column 0 as do targets, however, directives that follow targets must be indented. The variables shown in the first example can also default to the user's PATH:

        CC=gcc
        MAKE=make

Which is generally the norm for handcrafted Makefiles. Note that there is nothing that cannot be stuffed into a variable in make. In the following example several source files are being combined into one resulting executable - all of which are defined by variables:

        CC=gcc
        SRCS= hello.c world.c io.c
        BIN= hello_world
        COPTS= -O2 --warn           

        hello: all

        all:
                ${CC} ${COPTS} ${SRCS} -o ${BIN}

Library linking can also be specified:

        # libs to add
        LIBS= -lpcap
        ...
        all:
                ${CC} ${COPTS} ${LIBS} ${SRCS} -o ${BIN}

Note that the use of brackets is simpy the method I use as habit. GNU make generally uses parenths as well. Make has a great many other capabilities and there are several good online and dead tree resources for utilizing make.

A.0.4 Local Perl Libs

Sometimes a site has need for easily accessible Perl libraries. Three methods to go about keeping handy Perl code readily available are:

  1. Just keep a code repository of functions and dump them into programs on an as needed basis.
  2. Write a Perl library with a group of functions in them.
  3. Write Perl modules.

A text on this site discusses the first option, this text deals with the second option. All three bear discussion though. Within this article, setting up a library (not module) will be addressed. Weighing In

Many factors have to be weighed to argue either for or against simply yank putting versus a local "requires" versus writing a module.

The yank put method seems appropriate for low frequency deployment. An example is a one time or limited time program. Many system administrators use this method because, well managing libraries is no fun. When a small set of programs or low frequency of development exists is the effort worth it?

Creating local libraries using the "requires" include method in Perl is a middle ground. A shared local set of libraries may be created that the Perl administrator does not have to solely maintain. A possible scenario is the line between sysadmin and system programmer. It is safe to say that the programmer is a competent programmer but may not know much about administering a Perl installation on the system. By creating a shared area on the filesystem, the sysadmin and programmer can deposit their libraries and indeed - benefit from each other's work.

Going the "full monty" of modules requires a bit more work. Modules can be setup with shared access. To keep downloaded modules from interfering with local modules, some administrative work needs to be done. If a module is good enough, it may be uploaded to CPAN and installed as part of the site install. Note that perl libs - just files with some functions in them - can also be uploaded to CPAN.

The last item to discuss is documentation. Method one has no real strict rules for documentation. Method two does not either, generally documentation is included. Method three requires documentation. Implementation Examples

Since yanking/putting functions from one file to another is pretty self explanatory, a look at creating a local library is presented. Implementation Details

At the imaginary site is an archive nfs server. On the server are directories packed with archives and a set of files listing archives. How the archives are organized is not important, what is important is the number of growing Perl programs and scripts that access them. These particular bits do the same operations over and over:

The goal: put those tasks into a separate readable location to save time.

The Subroutines

        ##
        # load_file: Open a file, read each line into an array, close the
        #            file and return the array of lines.
        ##
        sub load_file 
        {
          my ($file) = shift;
          my @file_contents;

          open(FILE, $file) or die "Unable to open file $file: $!\n";
          @file_contents = <FILE>;
          close FILE;

          return @file_contents;
        }

        ##
        # load_dir: Open a directory, read each entry into an array, close
        #           the directory handle and return the array.
        ##
        sub load_dir 
        {
          my ($dir) = shift;  
          my @dir_contents;  

          opendir(DIR, $dir) or die "Unable to open dir $dir: $!\n";
          @dir_contents = readdir(DIR);
          close(DIR);

          return @dir_contents;
        }

        ##
        # rsort: Sort then reverse an array.
        ##
        sub rsort 
        {
          my (@contents) = @_;

          @contents = sort(@contents);

          return (reverse(@contents));  
        }

        ##
        # atomic_chomp: Chomp every item on an array, return the array.
        ##
        sub atomic_chomp
        {
          my (@list2chomp) = @_;

          foreach(@list2chomp) {
            chomp($_);
          }

          return @list2chomp;
        }

Documentation

Time to add documentation. Perldoc "tags" go in the source file as a stub. The markup is self explanatory:

        __END__
        =head1 DESCRIPTION

This small library provides four simple function: load a file into an array, load a directory into an array, sort and reverse an array and chomp every item in an array.

The usage for each is:

        load_file(filename);
        load_dir(dirname);
        rsort(@array);
        atomic_chomp(@array);
        =head1 EXAMPLES
         my @foo = load_dir("/home/mui");
         @foo = rsort(@foo);
         ($item1, $item2) = atomic_chomp($item1, $item2);
        =head1 PREREQUISITES
        none
        =head1 COREQUISITES
        none
        =cut

The now finished lib is ready:

        ##
        # load_file: Open a file, read each line into an array, close the
        #            file and return the array of lines.
        ##
        sub load_file
        { 
          my ($file) = shift;
          my @file_contents;

          open(FILE, $file) or die "Unable to open file $file: $!\n";
          @file_contents = <FILE>;
          close FILE;

          return @file_contents;
        } 

        ##  
        # load_dir: Open a directory, read each entry into an array, close
        #           the directory handle and return the array.
        ##
        sub load_dir
        {
          my ($dir) = shift;
          my @dir_contents;

          opendir(DIR, $dir) or die "Unable to open dir $dir: $!\n";
          @dir_contents = readdir(DIR);
          close(DIR);

          return @dir_contents;
        }

        ##
        # rsort: Sort then reverse an array.
        ##
        sub rsort
        {
          my (@contents) = @_;

          @contents = sort(@contents);

          return (reverse(@contents));
        }

        ##
        # atomic_chomp: Chomp every item on an array, return the array.
        ##
        sub atomic_chomp
        {
          my (@list2chomp) = @_;

          foreach(@list2chomp) {
            chomp($_);
          }

          return @list2chomp;
        }

        1; # Main - return a true value

        __END__
        =head1 DESCRIPTION

This small library provides four simple function: load a file into an array, load a directory into an array, sort and reverse an array and chomp every item in an array.

        =head1 EXAMPLES
        my @foo = load_dir("/home/mui");
        @foo = rsort(@foo);
        ($item1, $item2) = atomic_chomp($item1, $item2);

        =head1 PREREQUISITES

        none

        =head1 COREQUISITES

        none

        =cut

The only change is the addition of the ;1, a true return signal is needed to use the library. The Lib in Action

Time to take a look at the lib in action, first, the perldoc output:

        [jrf@vela:~$] perldoc atomic.pl
        ATOMIC(1)  User Contributed Perl Documentation ATOMIC(1)

        DESCRIPTION
          This small library provides four simple function: load a file into an
          array, load a directory into an array, sort and reverse an array and
          chomp every item in an array.

          The usage for each is:

              load_file(filename);
              load_dir(dirname);
              rsort(@array);
              atomic_chomp(@array);

        EXAMPLES
           my @foo = load_dir("/home/mui");
           @foo = rsort(@foo);
           ($item1, $item2) = atomic_chomp($item1, $item2);

        PREREQUISITES
          none

        COREQUISITES
          none

        perl v5.8.8            2006-05-16              ATOMIC(1)

An example of using the library:

        #!/usr/bin/env perl
        # atomtest - test out the atomic functions

        use strict;

        require "/path/to/atomic.pl";

        my @dirlist = load_dir("/etc");
        my @file    = load_file("/etc/passwd");

        @dirlist = rsort(@dirlist);        # Sort and reverse the directory
        @dirlist = atomic_chomp(@dirlist); # Chomp the directory list

        listall(@dirlist);
        listall(@file);
        sub listall 
        {
                my @list2print = @_;

                foreach(@list2print) {
                        print "$_\n";
                }
        }

Instead of cluttering the output atomtest is piped to wc:

        [jrf@vela:~$] ./atomtest | wc -l
        244

Summary

Creating a Perl library and sharing via a path is easy. Creating any shared libraries in an organization can save time and reduce headaches.

A.0.5 Return Values

Return values, as proven by practically every piece of interface documentation written - do not have to be strings. Actually, a return value can in fact be whatever the programmer wants the return to be regardless of the language. This is important because some languages follow a return by sanity clause. The Perl language returns whatever the last operation results were - much like the shell. A program written in C/C++ or Java can do the same thing, the only difference is a few more hoops might need to be jumped through to implement a non string return on a function that was designed to return a string. On the flip-side, there is also the guaranteed null return. If a string operation fails for whatever reason, the program builds a real NULL string for the said platform (which should be handled by glibc or libc) and thus guarantees a proper NULL return. The last and perhaps most effective is just - set a string value that is an equivalent to a signal. Using an error string value is literally, done all of the time. A set of predefined string names within the local context of a program simply mean something bad. In short, retval does not mean just numbers.

Does this Make void and no-ops bad?

Nope, one of the big beefs out there is Perl does insist on returning something which - well just may not be wanted. If it can be void then why should a programmer have to say so? The reverse policy should be used instead, if an implicit return is desired, then do so, if not - do not force people to do so. One of the big upsides to the C language is how retvals can be tossed out the window. Usually, automatic returns do no harm. There are cases when input buffering is being parsed by sub routines in Perl and shell scripts that can make forced returns a problem, such as buffer mangling, but in general - harmless. Using void functions is completely legitimate since 9 times out of 10 they are inconsequential.

Case Study: Checker Shell Script

There exist so many shell scripts that check system status that if one had a penny for each they may not be rich but they would be well to do. One of the dangers of shell scripting is in-the-box thinking, especially when on a team. In this case study there are two examples of a very simple shell script that exhibits two different behaviors.

The first script is the bad one, it acts only for itself and does nothing to help anyone else:

        ...
        for i in $#; do
                ssh $i uptime || logger -i -s "$0 could not reach ${i}"
        done
        ...
        exit 0

While syslog may be getting monitored somewhere, what if one wanted to use this "script" from another script?

A better script might offer a few alternatives to just logging:

        ...
        sshchk()
        {
                errors=0

                for i in $#; do
                        ssh $i uptime
                        if [ $? -gt 0 ]; then
                                logger -i -s "SSH Connect error to ${i}"
                                errors=$((errors+1))
                done

                return $errors          
        }
        ...
        exit $errors

Now at least the caller - shell or script - will know there was a problem.

It may even be permissible to say "at least one check failed" and do the following:

        ...
        sshchk()
        {
                errors=0

                for i in $#; do
                        ssh $i uptime
                        if [ $? -gt 0 ]; then
                                logger -i -s "SSH Connect error to ${i}"
                                errors=$((errors+1))
                done

                if [ "$error" -gt 0 ]; then
                        return 1
                fi
        }
        ...

In which case, something went wrong it just not known how many times.

In C Please

Many ideas in any programming language first come from inline testing. In line testing is a nice way of saying "cram it in using ifdefs." A good example of using returns in C is when if then else is not needed or may not even be applicable. Case Study: On the Side

In the example below, using pseudocode, a mount point is being checked and then an additional check is added, error on group or world readable. It does not matter if it is C, Perl or Java - the idea is the same. One version checks within the existing body of code while another, very succinctly, does not.

        if MOUNTPOINT
                return 0
        endif

In Line Version

        if MOUNTPOINT
                if EXEC
                        return 1
                if WORLDREAD
                        return 1
                if GROUPREAD
                        return 1
        return 0

Using retval to save the day...

        if MOUNTPOINT
                retval=check_mount_perms
                return retval
        ...
        check_mount_perms
                if EXEC
                        return 1
                if WORLDREAD
                        return 1
                if GROUPREAD
                        return 1
        return 0
        ...

So what does the additional function do? It does two things, one it takes the complexity of the check out of the simple mount point check. Next, it offers the ability to add or alter the checks on an as needed basis. What if checking for SGID or GGID were needed? What if just group and user were needed? In the latter version, such checks can be added without obfuscating the mount check too much.

What if more is needed? It is much easier to do this:

        if MOUNTPOINT
                retval=check_mount_perms
                return retval
        ...
        check_mount_perms
                if EXEC
                        return 1
                if WORLDREAD
                        return 1
                if GROUPREAD
                        return 1
                if GGID != MYGROUP
                        return 1
        return 0
        ...

Versus adding yet another check in the main calling program.

How and When is too Much?

The question of how much message passing versus actually doing work is as old as computational devices. There is no simple answer. The best answer, and I yet again refer to Paul Graham - is do what seems natural. Just keep in mind that well formed returns, whether they are strings, numbers or NULL are ultimately up to the writer - not the machine. When not to be Prudent

As mentioned earlier there are times when a program does not need to return a value, so when might that be? One good example is just information, the classic usage:

        void usage(void) {
                printf("Here is some info...",\n");
        }

always applies everywhere. A simple echo command etc. is just fine, even proven known operations, like flag checking, work great without needing explicit return values.

Summary

Return values help users and programmers alike every day. Making prudent use of them as a shell scripter, Perl monger or C programmer just makes it easier for all of us. The key part to remember is judicious use, if a check seems intrusive - just push it into a module and return something - otherwise just try to do the right thing.

A.0.6 Goofy Snippets

Over the years I have tried to keep a record of small interesting snippets I have found useful and somewhat out of the way. This appendice shows some of the more interesting ones.

A.0.6.i Inline Bash Function

All this stuffed into a bashrc:

        TotalBytes=0
        for Bytes in $(ls -l | grep "^-" | awk '{ print $5 }')
        do
         let TotalBytes=$TotalBytes+$Bytes
        done
        TotalMeg=$(echo -e "scale=3 \n$TotalBytes/1048576 \nquit" | bc)
        echo -n "$TotalMeg"
        rand_titles ()
        {
            if [ $TERM = xterm ]; then
                TLINES=`wc -l $HOME/.xterm_titles | awk '{print $1;}'`
                TLINE=$[ ${RANDOM} % $TLINES ];
                TLINE=$[$TLINE + 1];
                TITLE=`head -$TLINE < $HOME/.xterm_titles | tail -1`
                echo -ne "\033]0;${TITLE}\007"
            fi
        }
        PS1="[\u@\h:\w (\$(lsbytesum) Mb)]\$ "
        PROMPT_COMMAND='rand_titles'

Results in taking random titles out of .xterm_titles and making them the xterminal title plus showing filesizes.

A.0.6.ii Tiny Inline ASM in C

Although it rare these days (excepting video games perhaps) that one ever needs to do so here is a tiny example of including assembler in C:

        /* Simplistic example */
        int
        main(void)
        {
        __asm__ (" mov %ah,5");
                return 0;
        }

A.0.6.iii Tiny Inline C in Perl

Well if we looked at ASM in C it seems only fair to look at inline C in Perl:

#!/usr/bin/perl

        use Inline C => <<'END_C';
        void greet() {
                printf("Hello World\n");
        }
        END_C

        greet;

A.0.6.iv ISBN Processing Using LISP

The List Processing Language of LISP is very interesting and worth getting to know at a basic level. Here is a snippet of code that does the math for ISBN processing. An ISBN is built using four sets of numbers:

  1. group code
  2. publisher code
  3. id number assigned by publisher
  4. check digit

Using the number of 0-673-38582-5.
The check digit has 11 possible values: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, X(10)

The final digit is determined by multiplying the first 9 digits by 10, 9, 8, 7, 6, 5, 4, 3, 2 respectively then add them (in our example we will call this y). The check digit is then chosen so that y+check_digit is divisible by 11.

           [6]> (setq y-first (+ (* 10 0) (* 9 6) (* 8 7) (* 7 3) (* 6 3)))
           149
           [7]> (setq y-second (+ (* 5 8) (* 4 5) (* 3 8) (* 2 2)))
           88
           [8]> (setq y (+ y-first y-second))
           237
           [9]> (setq check-digit 5)
           5
           [10]> (setq mod 11)
           11
           [11]> (+ y check-digit)
           242
           [12]> (/ 242 mod)
           22

A.0.6.v Elementry Congruence in Python

lementry numerical congruence says m and n are integers and m!=0. Using the division algorithm n can be expressed as:

        #
        # n = qm + r,  where  0 <= r < |m|
        #

q is the quotient, r the remainder and |m| is absolute m. To prove this statement, a simple python session can help out:

        >>>q = 3
        >>>m = 9
        >>>r = 7
        >>>q * m + r
        >>>34

A.0.6.vi NASM Boot Floppy

While trying to troubleshoot a BIOS problem, I found this and made some minor modifications to it.

; read from a floppy to boot up

[ORG 0]

jmp 07C0h:start ; Goto segment 07C0

        ; message string
        msg     db      'Booting....'

        start:
                ; Update the segment registers
                mov ax, cs
                mov ds, ax
                mov es, ax

                mov si, msg     ; put the message string into si

        reset:                      ; Reset the floppy drive
                mov ax, 0           ;
                mov dl, 0           ; Drive=0 (=A)
                int 13h             ;
                jc reset            ; ERROR => reset again

        read:
                mov ax, 1000h       ; ES:BX = 1000:0000
                mov es, ax          ;
                mov bx, 0           ;

                mov ah, 2           ; Load disk data to ES:BX
                mov al, 5           ; Load 5 sectors
                mov ch, 0           ; Cylinder=0
                mov cl, 2           ; Sector=2
                mov dh, 0           ; Head=0
                mov dl, 0           ; Drive=0
                int 13h             ; Read!

                jc read             ; ERROR => Try again

                jmp 1000h:0000      ; Jump to the program

        times 510-($-$$) db 0
        dw 0AA55h