34.7. Assorted Tips

  • To keep a record of which user scripts have run during a particular sesssion or over a number of sessions, add the following lines to each script you want to keep track of. This will keep a continuing file record of the script names and invocation times.

    # Append (>>) following to end of each script tracked.
    date>> $SAVE_FILE      #Date and time.
    echo $0>> $SAVE_FILE   #Script name.
    echo>> $SAVE_FILE      #Blank line as separator.
    # Of course, SAVE_FILE defined and exported as environmental variable in ~/.bashrc
    # (something like ~/.scripts-run)

  • The >> operator appends lines to a file. What if you wish to prepend a line to an existing file, that is, to paste it in at the beginning?

    title="***This is the title line of data text file***"
    echo $title | cat - $file >$file.new
    # "cat -" concatenates stdout to $file.
    #  End result is
    #+ to write a new file with $title appended at *beginning*.

    Of course, sed can also do this.

  • A shell script may act as an embedded command inside another shell script, a Tcl or wish script, or even a Makefile. It can be invoked as an external shell command in a C program using the system() call, i.e., system("script_name");.

  • Put together files containing your favorite and most useful definitions and functions. As necessary, "include" one or more of these "library files" in scripts with either the dot (.) or source command.

    # ------ -------
    # Note:
    # No "#!" here.
    # No "live code" either.
    # Useful variable definitions
    ROOT_UID=0             # Root has $UID 0.
    E_NOTROOT=101          # Not root user error.
    MAXRETVAL=256          # Maximum (positive) return value of a function.
    # Functions
    Usage ()               # "Usage:" message.
      if [ -z "$1" ]       # No arg passed.
      echo "Usage: `basename $0` "$msg""
    Check_if_root ()       # Check if root running script.
    {                      # From "ex39.sh" example.
      if [ "$UID" -ne "$ROOT_UID" ]
        echo "Must be root to run this script."
        exit $E_NOTROOT
    CreateTempfileName ()  # Creates a "unique" temp filename.
    {                      # From "ex51.sh" example.
      suffix=`eval date +%s`
    isalpha2 ()            # Tests whether *entire string* is alphabetic.
    {                      # From "isalpha.sh" example.
      [ $# -eq 1 ] || return $FAILURE
      case $1 in
      *[!a-zA-Z]*|"") return $FAILURE;;
      *) return $SUCCESS;;
      esac                 # Thanks, S.C.
    abs ()                           # Absolute value.
    {                                # Caution: Max return value = 256.
      if [ -z "$1" ]                 # Need arg passed.
        return $E_ARGERR             # Obvious error value returned.
      if [ "$1" -ge 0 ]              # If non-negative,
      then                           #
        absval=$1                    # stays as-is.
      else                           # Otherwise,
        let "absval = (( 0 - $1 ))"  # change sign.
      return $absval
    tolower ()             #  Converts string(s) passed as argument(s)
    {                      #+ to lowercase.
      if [ -z "$1" ]       #  If no argument(s) passed,
      then                 #+ send error message
        echo "(null)"      #+ (C-style void-pointer error message)
        return             #+ and return from function.
      echo "$@" | tr A-Z a-z
      # Translate all passed arguments ($@).
    # Use command substitution to set a variable to function output.
    # For example:
    #    oldvar="A seT of miXed-caSe LEtTerS"
    #    newvar=`tolower "$oldvar"`
    #    echo "$newvar"    # a set of mixed-case letters
    # Exercise: Rewrite this function to change lowercase passed argument(s)
    #           to uppercase ... toupper()  [easy].

  • Use special-purpose comment headers to increase clarity and legibility in scripts.

    ## Caution.
    rm -rf *.zzy   ##  The "-rf" options to "rm" are very dangerous,
                   ##+ especially with wildcards.
    #+ Line continuation.
    #  This is line 1
    #+ of a multi-line comment,
    #+ and this is the final line.
    #* Note.
    #o List item.
    #> Another point of view.
    while [ "$var1" != "end" ]    #> while test "$var1" != "end"

  • A particularly clever use of if-test constructs is commenting out blocks of code.

    #  Try setting the above variable to something or other
    #+ for an unpleasant surprise.
    if [ $COMMENT_BLOCK ]; then
    Comment block --
    This is a comment line.
    This is another comment line.
    This is yet another comment line.
    echo "This will not echo."
    Comment blocks are error-free! Whee!
    echo "No more comments, please."
    exit 0

    Compare this with using here documents to comment out code blocks.

  • Using the $? exit status variable, a script may test if a parameter contains only digits, so it can be treated as an integer.

    test "$1" -ne 0 -o "$1" -eq 0 2>/dev/null
    # An integer is either equal to 0 or not equal to 0.
    # 2>/dev/null suppresses error message.
    if [ $? -ne "$SUCCESS" ]
      echo "Usage: `basename $0` integer-input"
      exit $E_BADINPUT
    let "sum = $1 + 25"             # Would give error if $1 not integer.
    echo "Sum = $sum"
    # Any variable, not just a command line parameter, can be tested this way.
    exit 0

  • The 0 - 255 range for function return values is a severe limitation. Global variables and other workarounds are often problematic. An alternative method for a function to communicate a value back to the main body of the script is to have the function write to stdout the "return value", and assign this to a variable.

    Example 34-10. Return value trickery

    # multiplication.sh
    multiply ()                     # Multiplies params passed.
    {                               # Will accept a variable number of args.
      local product=1
      until [ -z "$1" ]             # Until uses up arguments passed...
        let "product *= $1"
      echo $product                 #  Will not echo to stdout,
    }                               #+ since this will be assigned to a variable.
    mult1=15383; mult2=25211
    val1=`multiply $mult1 $mult2`
    echo "$mult1 X $mult2 = $val1"
                                    # 387820813
    mult1=25; mult2=5; mult3=20
    val2=`multiply $mult1 $mult2 $mult3`
    echo "$mult1 X $mult2 X $mult3 = $val2"
                                    # 2500
    mult1=188; mult2=37; mult3=25; mult4=47
    val3=`multiply $mult1 $mult2 $mult3 $mult4`
    echo "$mult1 X $mult2 X $mult3 X mult4 = $val3"
                                    # 8173300
    exit 0

    The same technique also works for alphanumeric strings. This means that a function can "return" a non-numeric value.

    capitalize_ichar ()          #  Capitalizes initial character
    {                            #+ of argument string(s) passed.
      string0="$@"               # Accepts multiple arguments.
      firstchar=${string0:0:1}   # First character.
      string1=${string0:1}       # Rest of string(s).
      FirstChar=`echo "$firstchar" | tr a-z A-Z`
                                 # Capitalize first character.
      echo "$FirstChar$string1"  # Output to stdout.
    newstring=`capitalize_ichar "each sentence should start with a capital letter."`
    echo "$newstring"          # Each sentence should start with a capital letter.

    It is even possible for a function to "return" multiple values with this method.

    Example 34-11. Even more return value trickery

    # sum-product.sh
    # A function may "return" more than one value.
    sum_and_product ()   # Calculates both sum and product of passed args.
      echo $(( $1 + $2 )) $(( $1 * $2 ))
    # Echoes to stdout each calculated value, separated by space.
    echo "Enter first number "
    read first
    echo "Enter second number "
    read second
    retval=`sum_and_product $first $second`      # Assigns output of function.
    sum=`echo "$retval" | awk '{print $1}'`      # Assigns first field.
    product=`echo "$retval" | awk '{print $2}'`  # Assigns second field.
    echo "$first + $second = $sum"
    echo "$first * $second = $product"
    exit 0
  • Next in our bag of trick are techniques for passing an array to a function, then "returning" an array back to the main body of the script.

    Passing an array involves loading the space-separated elements of the array into a variable with command substitution. Getting an array back as the "return value" from a function uses the previously mentioned strategem of echoing the array in the function, then invoking command substitution and the ( ... ) operator to assign it to an array.

    Example 34-12. Passing and returning arrays

    # array-function.sh: Passing an array to a function and...
    #                   "returning" an array from a function
    Pass_Array ()
      local passed_array   # Local variable.
      passed_array=( `echo "$1"` )
      echo "${passed_array[@]}"
      #  List all the elements of the new array
      #+ declared and set within the function.
    original_array=( element1 element2 element3 element4 element5 )
    echo "original_array = ${original_array[@]}"
    #                      List all elements of original array.
    # This is the trick that permits passing an array to a function.
    # **********************************
    argument=`echo ${original_array[@]}`
    # **********************************
    #  Pack a variable
    #+ with all the space-separated elements of the original array.
    # Note that attempting to just pass the array itself will not work.
    # This is the trick that allows grabbing an array as a "return value".
    # *****************************************
    returned_array=( `Pass_Array "$argument"` )
    # *****************************************
    # Assign 'echoed' output of function to array variable.
    echo "returned_array = ${returned_array[@]}"
    echo "============================================================="
    #  Now, try it again,
    #+ attempting to access (list) the array from outside the function.
    Pass_Array "$argument"
    # The function itself lists the array, but...
    #+ accessing the array from outside the function is forbidden.
    echo "Passed array (within function) = ${passed_array[@]}"
    # NULL VALUE since this is a variable local to the function.
    exit 0

    For a more elaborate example of passing arrays to functions, see Example A-11.

  • Using the double parentheses construct, it is possible to use C-like syntax for setting and incrementing variables and in for and while loops. See Example 10-12 and Example 10-17.

  • A useful scripting technique is to repeatedly feed the output of a filter (by piping) back to the same filter, but with a different set of arguments and/or options. Especially suitable for this are tr and grep.

    # From "wstrings.sh" example.
    wlist=`strings "$1" | tr A-Z a-z | tr '[:space:]' Z | \
    tr -cs '[:alpha:]' Z | tr -s '\173-\377' Z | tr Z ' '`

    Example 34-13. Fun with anagrams

    # agram.sh: Playing games with anagrams.
    # Find anagrams of...
    anagram "$LETTERSET" | # Find all anagrams of the letterset...
    grep '.......' |       # With at least 7 letters,
    grep '^is' |           # starting with 'is'
    grep -v 's$' |         # no plurals
    grep -v 'ed$'          # no past tense verbs
    #  Uses "anagram" utility
    #+ that is part of the author's "yawl" word list package.
    #  http://ibiblio.org/pub/Linux/libs/yawl-0.2.tar.gz
    exit 0                 # End of code.
    bash$ sh agram.sh

    See also Example 28-2, Example 12-18, and Example A-10.

  • Use "anonymous here documents" to comment out blocks of code, to save having to individually comment out each line with a #. See Example 17-10.

  • Running a script on a machine that relies on a command that might not be installed is dangerous. Use whatis to avoid potential problems with this.

    CMD=command1                 # First choice.
    PlanB=command2               # Fallback option.
    command_test=$(whatis "$CMD" | grep 'nothing appropriate')
    #  If 'command1' not found on system , 'whatis' will return
    #+ "command1: nothing appropriate."
    if [[ -z "$command_test" ]]  # Check whether command present.
      $CMD option1 option2       #  Run command1 with options.
    else                         #  Otherwise,
      $PlanB                     #+ run command2.

  • The run-parts command is handy for running a set of command scripts in sequence, particularly in combination with cron or at.

  • It would be nice to be able to invoke X-Windows widgets from a shell script. There happen to exist several packages that purport to do so, namely Xscript, Xmenu, and widtools. The first two of these no longer seem to be maintained. Fortunately, it is still possible to obtain widtools here.


    The widtools (widget tools) package requires the XForms library to be installed. Additionally, the Makefile needs some judicious editing before the package will build on a typical Linux system. Finally, three of the six widgets offered do not work (and, in fact, segfault).

    For more effective scripting with widgets, try Tk or wish (Tcl derivatives), PerlTk (Perl with Tk extensions), tksh (ksh with Tk extensions), XForms4Perl (Perl with XForms extensions), Gtk-Perl (Perl with Gtk extensions), or PyQt (Python with Qt extensions).