|
Debugging in Shell script |
|
|
|
|
Written by Hemanshu Patel
|
|
Tuesday, 23 September 2008 |
|
Page 1 of 3 Debugging in Shell script The Bash shell contains no built-in debugger, and only bare-bones debugging-specific commands and constructs. Syntax errors or outright typos in the script generate cryptic error messages that are often of no help in debugging a non-functional script.
Example 29-1. A buggy script #!/bin/bash # ex74.sh
# This is a buggy script. # Where, oh where is the error?
a=37
if [$a -gt 27 ] then echo $a fi
exit 0 |
Output from script: ./ex74.sh: [37: command not found | What's wrong with the above script? Hint: after the if.Example 29-2. Missing keyword #!/bin/bash # missing-keyword.sh: What error message will this generate?
for a in 1 2 3 do echo "$a" # done # Required keyword 'done' commented out in line 7.
exit 0 |
Output from script: missing-keyword.sh: line 10: syntax error: unexpected end of file | Note that the error message does not necessarily reference the line in which the error occurs, but the line where the Bash interpreter finally becomes aware of the error. Error messages may disregard comment lines in a script when reporting the line number of a syntax error. What if the script executes, but does not work as expected? This is the all too familiar logic error. Example 29-3. test24: another buggy script #!/bin/bash
# This script is supposed to delete all filenames in current directory #+ containing embedded spaces. # It doesn't work. # Why not?
badname=`ls | grep ' '`
# Try this: # echo "$badname"
rm "$badname"
exit 0 |
Try to find out what's wrong with Example by uncommenting the echo "$badname" line. Echo statements are useful for seeing whether what you expect is actually what you get. In this particular case, rm "$badname" will not give the desired results because $badname should not be quoted. Placing it in quotes ensures that rm has only one argument (it will match only one filename). A partial fix is to remove to quotes from $badname and to reset $IFS to contain only a newline, IFS=$'\n'. However, there are simpler ways of going about it. # Correct methods of deleting filenames containing spaces. rm *\ * rm *" "* rm *' '* # Thank you. S.C. | Summarizing the symptoms of a buggy script, It bombs with a "syntax error" message, or It runs, but does not work as expected (logic error). It runs, works as expected, but has nasty side effects (logic bomb).
Tools for debugging non-working scripts include Inserting echo statements at critical points in the script to trace the variables, and otherwise give a snapshot of what is going on.  | Even better is an echo that echoes only when debug is on. ### debecho (debug-echo), by Stefano Falsetto ### ### Will echo passed parameters only if DEBUG is set to a value. ### debecho () { if [ ! -z "$DEBUG" ]; then echo "$1" >&2 # ^^^ to stderr fi }
DEBUG=on Whatever=whatnot debecho $Whatever # whatnot
DEBUG= Whatever=notwhat debecho $Whatever # (Will not echo.) |
|
Using the tee filter to check processes or data flows at critical points. Setting option flags -n -v -x sh -n scriptname checks for syntax errors without actually running the script. This is the equivalent of inserting set -n or set -o noexec into the script. Note that certain types of syntax errors can slip past this check. sh -v scriptname echoes each command before executing it. This is the equivalent of inserting set -v or set -o verbose in the script. The -n and -v flags work well together. sh -nv scriptname gives a verbose syntax check. sh -x scriptname echoes the result each command, but in an abbreviated manner. This is the equivalent of inserting set -x or set -o xtrace in the script.
Inserting set -u or set -o nounset in the script runs it, but gives an unbound variable error message at each attempt to use an undeclared variable. Using an "assert" function to test a variable or condition at critical points in a script. (This is an idea borrowed from C.) Example 29-4. Testing a condition with an assert #!/bin/bash # assert.sh
####################################################################### assert () # If condition false, { #+ exit from script #+ with appropriate error message. E_PARAM_ERR=98 E_ASSERT_FAILED=99
if [ -z "$2" ] # Not enough parameters passed then #+ to assert() function. return $E_PARAM_ERR # No damage done. fi
lineno=$2
if [ ! $1 ] then echo "Assertion failed: \"$1\"" echo "File \"$0\", line $lineno" # Give name of file and line number. exit $E_ASSERT_FAILED # else # return # and continue executing the script. fi } # Insert a similar assert() function into a script you need to debug. #######################################################################
a=5 b=4 condition="$a -lt $b" # Error message and exit from script. # Try setting "condition" to something else #+ and see what happens.
assert "$condition" $LINENO # The remainder of the script executes only if the "assert" does not fail.
# Some commands. # Some more commands . . . echo "This statement echoes only if the \"assert\" does not fail." # . . . # More commands . . .
exit $? |
Using the $LINENO variable and the caller builtin. Trapping at exit. The exit command in a script triggers a signal 0, terminating the process, that is, the script itself. It is often useful to trap the exit, forcing a "printout" of variables, for example. The trap must be the first command in the script.
|