33.3. Shell Wrappers

A wrapper is a shell script that embeds a system command or utility, that accepts and passes a set of parameters to that command. [1] Wrapping a script around a complex command line simplifies invoking it. This is expecially useful with sed and awk.

A sed or awk script would normally be invoked from the command line by a sed -e 'commands' or awk 'commands'. Embedding such a script in a Bash script permits calling it more simply, and makes it reusable. This also enables combining the functionality of sed and awk, for example piping the output of a set of sed commands to awk. As a saved executable file, you can then repeatedly invoke it in its original form or modified, without the inconvenience of retyping it on the command line.


Example 33-1. shell wrapper

   1 #!/bin/bash
   2 
   3 # This simple script removes blank lines from a file.
   4 # No argument checking.
   5 #
   6 # You might wish to add something like:
   7 #
   8 # E_NOARGS=85
   9 # if [ -z "$1" ]
  10 # then
  11 #  echo "Usage: `basename $0` target-file"
  12 #  exit $E_NOARGS
  13 # fi
  14 
  15 
  16 
  17 sed -e /^$/d "$1"
  18 # Same as
  19 #    sed -e '/^$/d' filename
  20 # invoked from the command line.
  21 
  22 #  The '-e' means an "editing" command follows (optional here).
  23 #  '^' indicates the beginning of line, '$' the end.
  24 #  This matches lines with nothing between the beginning and the end --
  25 #+ blank lines.
  26 #  The 'd' is the delete command.
  27 
  28 #  Quoting the command-line arg permits
  29 #+ whitespace and special characters in the filename.
  30 
  31 #  Note that this script doesn't actually change the target file.
  32 #  If you need to do that, redirect its output.
  33 
  34 exit


Example 33-2. A slightly more complex shell wrapper

   1 #!/bin/bash
   2 
   3 #  subst.sh: a script that substitutes one pattern for
   4 #+ another in a file,
   5 #+ i.e., "sh subst.sh Smith Jones letter.txt".
   6 #                     Jones replaces Smith.
   7 
   8 ARGS=3         # Script requires 3 arguments.
   9 E_BADARGS=85   # Wrong number of arguments passed to script.
  10 
  11 if [ $# -ne "$ARGS" ]
  12 # Test number of arguments to script (always a good idea).
  13 then
  14   echo "Usage: `basename $0` old-pattern new-pattern filename"
  15   exit $E_BADARGS
  16 fi
  17 
  18 old_pattern=$1
  19 new_pattern=$2
  20 
  21 if [ -f "$3" ]
  22 then
  23     file_name=$3
  24 else
  25     echo "File \"$3\" does not exist."
  26     exit $E_BADARGS
  27 fi
  28 
  29 
  30 # -----------------------------------------------
  31 #  Here is where the heavy work gets done.
  32 sed -e "s/$old_pattern/$new_pattern/g" $file_name
  33 # -----------------------------------------------
  34 
  35 #  's' is, of course, the substitute command in sed,
  36 #+ and /pattern/ invokes address matching.
  37 #  The 'g,' or global flag causes substitution for EVERY
  38 #+ occurence of $old_pattern on each line, not just the first.
  39 #  Read the 'sed' docs for an in-depth explanation.
  40 
  41 exit $?


Example 33-3. A generic shell wrapper that writes to a logfile

   1 #!/bin/bash
   2 #  Generic shell wrapper that performs an operation
   3 #+ and logs it.
   4 
   5 # Must set the following two variables.
   6 OPERATION=
   7 #         Can be a complex chain of commands,
   8 #+        for example an awk script or a pipe . . .
   9 LOGFILE=
  10 #         Command-line arguments, if any, for the operation.
  11 
  12 
  13 OPTIONS="$@"
  14 
  15 
  16 # Log it.
  17 echo "`date` + `whoami` + $OPERATION "$@"" >> $LOGFILE
  18 # Now, do it.
  19 exec $OPERATION "$@"
  20 
  21 # It's necessary to do the logging before the operation.
  22 # Why?


Example 33-4. A shell wrapper around an awk script

   1 #!/bin/bash
   2 # pr-ascii.sh: Prints a table of ASCII characters.
   3 
   4 START=33   # Range of printable ASCII characters (decimal).
   5 END=127    # Will not work for unprintable chars. (> 127).
   6 
   7 echo " Decimal   Hex     Character"   # Header.
   8 echo " -------   ---     ---------"
   9 
  10 for ((i=START; i<=END; i++))
  11 do
  12   echo $i | awk '{printf("  %3d       %2x         %c\n", $1, $1, $1)}'
  13 # The Bash printf builtin will not work in this context:
  14 #     printf "%c" "$i"
  15 done
  16 
  17 exit 0
  18 
  19 
  20 #  Decimal   Hex     Character
  21 #  -------   ---     ---------
  22 #    33       21         !
  23 #    34       22         "
  24 #    35       23         #
  25 #    36       24         $
  26 #
  27 #    . . .
  28 #
  29 #   122       7a         z
  30 #   123       7b         {
  31 #   124       7c         |
  32 #   125       7d         }
  33 
  34 
  35 #  Redirect the output of this script to a file
  36 #+ or pipe it to "more":  sh pr-asc.sh | more


Example 33-5. A shell wrapper around another awk script

   1 #!/bin/bash
   2 
   3 # Adds up a specified column (of numbers) in the target file.
   4 
   5 ARGS=2
   6 E_WRONGARGS=65
   7 
   8 if [ $# -ne "$ARGS" ] # Check for proper no. of command line args.
   9 then
  10    echo "Usage: `basename $0` filename column-number"
  11    exit $E_WRONGARGS
  12 fi
  13 
  14 filename=$1
  15 column_number=$2
  16 
  17 #  Passing shell variables to the awk part of the script is a bit tricky.
  18 #  One method is to strong-quote the Bash-script variable
  19 #+ within the awk script.
  20 #     $'$BASH_SCRIPT_VAR'
  21 #      ^                ^
  22 #  This is done in the embedded awk script below.
  23 #  See the awk documentation for more details.
  24 
  25 # A multi-line awk script is invoked by:  awk ' ..... '
  26 
  27 
  28 # Begin awk script.
  29 # -----------------------------
  30 awk '
  31 
  32 { total += $'"${column_number}"'
  33 }
  34 END {
  35      print total
  36 }     
  37 
  38 ' "$filename"
  39 # -----------------------------
  40 # End awk script.
  41 
  42 
  43 #   It may not be safe to pass shell variables to an embedded awk script,
  44 #+  so Stephane Chazelas proposes the following alternative:
  45 #   ---------------------------------------
  46 #   awk -v column_number="$column_number" '
  47 #   { total += $column_number
  48 #   }
  49 #   END {
  50 #       print total
  51 #   }' "$filename"
  52 #   ---------------------------------------
  53 
  54 
  55 exit 0

For those scripts needing a single do-it-all tool, a Swiss army knife, there is Perl. Perl combines the capabilities of sed and awk, and throws in a large subset of C, to boot. It is modular and contains support for everything ranging from object-oriented programming up to and including the kitchen sink. Short Perl scripts lend themselves to embedding in shell scripts, and there may be some substance to the claim that Perl can totally replace shell scripting (though the author of the ABS Guide remains skeptical).


Example 33-6. Perl embedded in a Bash script

   1 #!/bin/bash
   2 
   3 # Shell commands may precede the Perl script.
   4 echo "This precedes the embedded Perl script within \"$0\"."
   5 echo "==============================================================="
   6 
   7 perl -e 'print "This is an embedded Perl script.\n";'
   8 # Like sed, Perl also uses the "-e" option.
   9 
  10 echo "==============================================================="
  11 echo "However, the script may also contain shell and system commands."
  12 
  13 exit

It is even possible to combine a Bash script and Perl script within the same file. Depending on how the script is invoked, either the Bash part or the Perl part will execute.


Example 33-7. Bash and Perl scripts combined

   1 #!/bin/bash
   2 # bashandperl.sh
   3 
   4 echo "Greetings from the Bash part of the script, $0."
   5 # More Bash commands may follow here.
   6 
   7 exit
   8 # End of Bash part of the script.
   9 
  10 # =======================================================
  11 
  12 #!/usr/bin/perl
  13 # This part of the script must be invoked with
  14 #    perl -x bashandperl.sh
  15 
  16 print "Greetings from the Perl part of the script, $0.\n";
  17 #      Perl doesn't seem to like "echo" ...
  18 # More Perl commands may follow here.
  19 
  20 # End of Perl part of the script.

 bash$ bash bashandperl.sh
 Greetings from the Bash part of the script.
 
 
 bash$ perl -x bashandperl.sh
 Greetings from the Perl part of the script.
 	      

One interesting example of a complex shell wrapper is Martin Matusiak's undvd script, which provides an easy-to-use command-line interface to the complex mencoder utility. Another example is Itzchak Rehberg's Ext3Undel, a set of scripts to recover deleted file on an ext3 filesystem.

Notes

[1]

Quite a number of Linux utilities are, in fact, shell wrappers. Some examples are /usr/bin/pdf2ps, /usr/bin/batch, and /usr/X11R6/bin/xmkmf.