Debugging

Glenn Norman
  1. Unix Shell Scripting
  2. Shell Basics
  3. Testing in bash
  4. Capturing User Input
  5. Scripting : Exercise 1
  6. Debugging
  7. Bourne/Bash/Korn Commands
  8. Shell Variables
  9. IO Redirection
  10. Pipes
  11. Operators, Wildcards and Expressions
  12. Flow Control
  13. Scripting : Exercise 2
  14. Shell Differences
  15. String Functions
  16. awk
  17. xargs
  18. Power Tools
  19. Exercise 3

There are several very elementary techniques you can use to debug your scripts.

Switch Your Shell to Execute (Debug) Mode

Remember the Order of Operations? You can change the mode of the Bourne/bash/Korn shell to show you what your command looks like after the replacement operations are performed by commanding:

sh -x # substitute "bash" or "ksh" as necessary

You have just opened a sub-shell. You must type “exit” to leave it.

When you run a command, wildcards are replaced, variables are expanded, embedded commands are run, and then the final resulting command is actually sent to the Bourne/bash/Korn interpreter. You’ll see this resulting command, after you press the Enter key, on the line below your command, preceeded by a + sign.

You can type:

sh +x

to turn “off” debugging, but what you’ve actually done is open yet another subshell. You’ll need to command “exit” to leave this one too.

Checking Your Return Value (Error Code)

The most basic way to test for the success of a single command is to examine its error code. The error code is always returned whenever you run anything; it’s usually just not checked. Your job, then, is to check it.

First, run a command:

ls /bin

Then check its return value with this command:

echo $?

You can examine this value one command at a time, or you can include these tests within a script:

cat foo # this is "command 1", and can be any command

if [ $? -ne 0 ]; then
echo "Error at command 1!"
# every time you use this code block, increment the number
# so you can tell where errors occurred 
else
echo "command 1 worked"
# comment out the line above after development
fi

This is especially valuable when you’re passing variables around:

echo "$myvar" # this is "command 2" in this case
if [ $? -ne 0 ]; then
echo "Error at command 2! \$myvar doesn't exist."
else
echo "command 2 worked"
# comment out the line above after development
fi

Echoing Values During Development

It’s also highly useful to check the values you’re passing around by echoing them back during testing.

#!/bin/bash

echo -n "Username: "
read username

# test the value of username
echo "username is $username"
# then comment this line out later

if [ $username = root ]; then
echo "Welcome to FooSoft 3.0"
foosoft.sh
else
echo "You must be root to run this script"
fi

Creating Your Own Error Codes

Even more useful is designating your own error codes. Yes, you do get to do that: you’re not just stuck with “0” or “1”.

#!/bin/bash

echo -n "Username: "
read username

# test the value of username
echo "usernameis $username"
# then comment this line out later

if [ $username = root ]; then
echo "Welcome to FooSoft 3.0"
foosoft.sh
else
echo "You must be root to run this script"
exit 666
fi

Note that exit 666. If you use this, and test your error code:

echo $?

you’d get this if an invalid user tried to log in:

666

Building Debugging Into Your Script

Here’s the ultimate step: building debugging into your script. Near the top of your script, define and set a variable:

debugging=1
# set to 0 to turn off debugging

Then, at frequent intervals in your code, trigger messages telling you the value of variables, your position in the routine or any other information:

#!/bin/bash

echo -n "Username: "
read username

debugging=1

if [ $debugging -eq 1 ]; then
echo "Value of \$username is " $username
else
# bash will complain if you don't do at least
# something in an else
# though an else is not technically necessary here
echo -n ""
fi

Now you can switch your debug information on and off at a single location, by changing the value of the debugging variable.