Originally Published: Thursday, 2 December 1999 Author: Brian Watson
Published to: develop_articles_tutorials/Development Tutorials Page: 1/1 - [Printable]

Shell Script Debugging Techniques

The shell can be a wonderful programming language, great for writing quick-and-dirty hacks, useful for prototyping larger projects, and sometimes the easiest way to get what you need to do, done.

   Page 1 of 1  

The shell can be a wonderful programming language, great for writing quick-and-dirty hacks, useful for prototyping larger projects, and sometimes the easiest way to get what you need to do, done.

But what about debugging? There's nothing more irritating than forgetting an obscure little punctuation mark, since bash will usually just keep plugging along. Say you're testing the returned result of a command such as:

  if ping -c1 yahoo.com &>/dev/null

Any error message that would be printed to the screen will go straight to /dev/null. In this example, assume we don't want any output from the ping command, but we just want to test its return status: success or failure. The shell doesn't let us test which error we caused, just whether the command succeeded or not! In other words, there's no way to tell a "command not found" error from a "no route to host," other than looking at the output, which we can't see because we redirected it!

You can of course use sh -x script.sh to see a sort of debug trace. However, there are some other things you can do. Suppose you are writing a script that does something that you don't want to do each time the script is executed, like deleting a file, or bringing down a network interface, or something similar. If your script downloads a 10 MB file, you'll quickly tire of waiting for the download to finish, if all you want to do is make sure your dialog boxes are properly formatted... pressing Ctrl-C doesn't help.

An example of a quick way to debug would be:

  if [ "$1" = "--fake" ]
    then
      echo "rm -rf /usr/src/linux"
    else
      rm -rf /usr/src/linux
  fi

This makes for sloppy code, which is hard to read and hard to maintain. Some purists would, of course, consider the following method even less readable, but if you're wise to the ways of bash-as-language, it becomes second nature to do variable interpolation in your head.

For instance you could set RM="rm" and then use ${RM} filename, which is still fairly readable, and offers the advantage that the variable RM could instead be set to some other value, such as RM="echo rm". In this way, you can test the logic of your program without actually executing those "destructive" commands.

Try this:   ---begin kernelget.sh---
  #!/bin/sh

  FAKE=""

  [ "$1" = "--fake" ] && { FAKE="echo "
        shift }

  WGET="${FAKE}wget"
  TAR="${FAKE}tar"
  RM="${FAKE}rm"
  PAUSE=
  [ "$FAKE" != "" ] && PAUSE="read junk"

  dialog --inputbox "Which kernel version would you like to download?" 10 80 \
    2> /tmp/ver
  VER=`cat /tmp/ver`
  MAJOR=`awk -F . '{ print $1"."$2 }' < /tmp/ver`
  cd /tmp
  dialog --infobox "Now wgetting version $VER" 6 40
  if ! ${WGET} -q ftp://ftp.us.kernel.org/pub/linux/kernel/v$MAJOR/linux-$VER.tar.gz
    then
      echo "${WGET} failed."
      exit 1
  fi
  ${PAUSE}
  dialog --infobox "Done getting; now decompressing" 6 40
  cd /usr/src
  ${RM} -f linux
  ${TAR} xvfz /tmp/linux-$VER.tar.gz > /dev/null
  ${PAUSE}
  cd linux
  dialog --msgbox "Done uncompressing, now run make menuconfig" 10 80

  ---end kernelget.sh---

Note that this script does no real error checking, but for demonstration purposes, assume you're trying to debug this. Now suppose you want to test the formatting of the dialog boxes. You can call this as:

  kernelget.sh --fake

Alternatively, try:

  sh -x kernelget.sh --fake 2> kernelget.out

(This is used to save all those commands that you can't see because "dialog" wipes the screen out before you can read them.)

The script will just print out the commands instead of running them (which will save you a great deal of time, especially on a slow network connection), but is still easy to understand, for the casual observer.

Our script doesn't take any parameters, but if it did, they would be preserved by the shift command in line 6. This means you can add this kind of debugging to any script without worrying about breaking any existing code that parses the command line.

Brian Watson





   Page 1 of 1