Learning_the_bash_Shell_Third_Edition 17/n

CHAPTER 9|Debugging Shell Programs

This chapter looks at some useful features that you can use to debug shell programs.We’ll look at how you can utilize them in the first part of this chapter. We’ll then look at some powerful new features of bash, not present in most Bourne shell workalikes, which will help in building a shell script debugging tool. At the end of the chapter, we’ll show step by step how to build a debugger for bash. The debugger,called bashdb, is a basic yet functional program that will not only serve as an extended example of various shell programming techniques, but will also provide you with a useful tool for examining the workings of your own shell scripts.

Basic Debugging Aids

Set Options

set -o option

Command-line option

Action

noexec -n Don’t run commands; check for syntax errors only
verbose -v Echo commands before running them
xtrace -x Echo commands after command-line processing

Fake Signals 

four fake signals available in bash.

Fake signal

Sent when

EXIT

The shell exits from script

ERR

A command returning a non-zero exit status

DEBUG

The shell has executed a statement.

The DEBUG signal is not available in bash versions prior to 2.0.

RETURN

A shell function or a script executed with the . or source builtins finishes executing.

The RETURN signal is not available in bash versions prior to 3.0.

EXIT

#when the number is correct, the echo message won't be showed in the terminal, but exit the terminal directly

cor@debian:~/shell/mar17$ cat 1.sh 
trap 'echo Thank you for playing!' EXIT

magicnum=$(($RANDOM%10+1))
echo 'Guess a number between 1 and 10:'
echo "magicnum = "$magicnum
while read -p 'Guess: ' guess;do
    sleep 1
    if [ "$guess" = $magicnum ];then
        echo 'Right!'
        exit
    fi
    echo 'Wrong!'
done

#when the number is correct, the echo message won't be showed in the terminal, but exit the terminal directly

 

cor@debian:~/shell/mar17$ . 1.sh
Guess a number between 1 and 10:
magicnum = 3
Guess: 1
Wrong!
Guess: 2
Wrong!
Guess: 4
Wrong!
Guess: 5
Wrong!
Guess: 6
Wrong!
Guess: 7
Wrong!
Guess: 8
Wrong!
Guess: 9
Wrong!
Guess: 10
Wrong!
Guess:

# it seems the quickly exit is because it goes too fast, let me try adding a  sleep there:

trap 'echo Thank you for playing!' EXIT

magicnum=$(($RANDOM%10+1))
echo 'Guess a number between 1 and 10:'
echo "magicnum = "$magicnum
while read -p 'Guess: ' guess;do
    sleep 1
    if [ "$guess" = $magicnum ];then
        echo 'Right!'
        sleep 20
        exit
    fi
    echo 'Wrong!'
done

#yes that's it:

Guess a number between 1 and 10:
magicnum = 5
Guess: 3
Wrong!
Guess: 4
Wrong!
Guess: 5
Right!

 ERR 

DEBUG

For example, you notice the value of a particular variable is running amok. The naive approach is to put in a lot of echo statements to check the variable’s value at several points. The DEBUG trap makes this easier by letting you do this:

function dbgtrap
{
echo "badvar is $badvar"
}
trap dbgtrap DEBUG
...section of code in which the problem occurs...
trap - DEBUG
# turn off the DEBUG trap

  

This code will print the value of the wayward variable before every statement between the two traps.

One important point to remember when using DEBUG is that it is not inherited by functions called from the shell in which it is set. In other words, if your shell sets a DEBUG trap and then calls a function, the statements within the function will not execute the trap. There are three ways around this. Firstly you can set a trap for DEBUG explicitly within the function. Alternately you can declare the function with the -t option which turns on debug inheritance in functions and allows a function to inherit a DEBUG trap from the caller. Lastly you can use set -o functrace (or set -T)which does the same thing as declare but applies to all functions

 RETURN

As with DEBUG, the RETURN trap is not inherited by functions. You again have the options of setting the trap for RETURN within the function, declare the function with the -t option so that that function inherits the trap, or use set -o functrace to turn on the inheritance for all functions.

Here is a simple example of a RETURN trap:

function returntrap {
echo "A return occurred"
}
trap returntrap RETURN
function hello {
echo "hello world"
}
hello

  Debugging Variables

  

Bash 3.0 added some useful environment variables to aid in writing a debugger.These include BASH_SOURCE, which contains an array of filenames that correspond to what is currently executing; BASH_LINENO, which is an array of line numbers that correspond to function calls that have been made; BASH_ARGC and BASH_ARGV array variables, the first holding the number of parameters in each frame and the second the parameters themselves.We’ll now look at writing a debugger, although we’ll keep things simple and avoid using these variables. This also means the debugger will work with earlier versions of bash.

 A bash Debugger

Specifically, we’ll provide the ability to:

• Specify places in the program at which to stop execution. These are called breakpoints.

• Execute a specified number of statements in the program. This is called stepping.

• Examine and change the state of the program during its execution. This includes being able to print out the values of variables and change them when the program is stopped at a breakpoint or after stepping.

• Print out the source code we are debugging along with indications of where breakpoints are and what line in the program we are currently executing.

• Provide the debugging capability without having to change the original source code of the program we wish to debug in any way.

As you will see, the capability to do all of these things (and more) is easily provided by the constructs and methods we have seen in previous chapters.

 Structure of the Debugger

 The driver script

The driver script is responsible for setting everything up. It is a script called bashdb and looks like this:

# bashdb - a bash debugger
# Driver Script: concatenates the preamble and the target script
# and then executes the new script.
echo 'bash Debugger version 1.0'
_dbname=${0##*/}
if (( $# < 1 )) ; then
echo "$_dbname: Usage: $_dbname filename" >&2
exit 1
fi
_guineapig=$1
if [ ! -r $1 ]; then
echo "$_dbname: Cannot read file '$_guineapig'." >&2
exit 1
fi
shift
_tmpdir=/tmp
_libdir=.
_debugfile=$_tmpdir/bashdb.$$ # temporary file for script that is
being debugged
cat $_libdir/bashdb.pre $_guineapig > $_debugfile
exec bash $_debugfile $_guineapig $_tmpdir $_libdir "$@"

  exec

The last line runs the newly created script with exec, a statement we haven’t discussed yet. We’ve chosen to wait until now to introduce it because—as we think you’ll agree—it can be dangerous. exec takes its arguments as a command line and runs the command in place of the current program, in the same process. In other words, a shell that runs exec will terminate immediately and be replaced by exec’s arguments.

 The Preamble

Now we’ll look at the code that gets prepended to the guinea pig script; we call this the preamble. It’s kept in the file bashdb.pre and looks like this:

#
#
#
#
#
#
bashdb preamble
This file gets prepended to the shell script being debugged.
Arguments:
$1 = the name of the original guinea pig script
$2 = the directory where temporary files are stored
$3 = the directory where bashdb.pre and bashdb.fns are stored
_debugfile=$0
_guineapig=$1
_tmpdir=$2
_libdir=$3
shift 3
source $_libdir/bashdb.fns
_linebp=
let _trace=0
let _i=1
while read; do
_lines[$_i]=$REPLY
let _i=$_i+1
done < $_guineapig
trap _cleanup EXIT
let _steps=1
trap '_steptrap $(( $LINENO -29 ))' DEBUG

  Debugger Functions

The function _steptrap is the entry point into the debugger; it is defined in the file bashdb.fns. Here is _steptrap:

# After each line of the test script is executed the shell traps to
# this function.
function _steptrap
{
_curline=$1
# the number of the line that just ran
(( $_trace )) && _msg "$PS4 line $_curline: ${_lines[$_curline]}"
if (( $_steps >= 0 )); then
let _steps="$_steps - 1"
fi
# First check to see if a line number breakpoint was reached.
# If it was, then enter the debugger.
if _at_linenumbp ; then
_msg "Reached breakpoint at line $_curline"
_cmdloop
# It wasn't, so check whether a break condition exists and is true.
# If it is, then enter the debugger.
elif [ -n "$_brcond" ] && eval $_brcond; then
_msg "Break condition $_brcond true at line $_curline"
_cmdloop
# It wasn't, so check if we are in step mode and the number of steps
# is up. If it is then enter the debugger.
elif (( $_steps == 0 )); then
_msg "Stopped at line $_curline"
_cmdloop
fi
}

  Commands

We will explain shortly how _steptrap determines these things; now we will look at _cmdloop. It’s a simple combination of the case statements we saw in Chapter 5,and the calculator loop we saw in the previous chapter.

# The Debugger Command Loop

function _cmdloop {
    local cmd args

    while read -e -p "bashdb> " cmd args;do
        case $cmd in
            ?|h ) _menu ;;        #print command menu
            bc ) _setbc $args ;;   #set a break condition
            bp ) _setbp $args ;;   #set a breakpoint at hte given
                                   #line
            cb ) _clearbp $args ;; #clear on or all breakpoints
            ds ) _displayscript ;; #list the script and show the
                                   #breakpoints
            g  ) return ;;         #"go": start/resume execution of
                                   #the script
            q  ) exit ;;           #quit
            s  ) let _steps=${args:-1} # single step N times
                                       # (default =1)
                return
            x ) _xtrace ;;             # toggle execution trace
            !* ) eval ${cmd#!} $args ;; # pass to the shell
            * ) _msg "Invalid command: '$cmd'" ;;
        esac
    done
}

  summarizes the debugger commands.

Command

Action

bp N

Set breakpoint at line N

bp

List breakpoints and break condition

bc string

Set break condition to string

bc

Clear break condition

cb N

Clear breakpoint at line N

cb

Clear all breakpoints

ds

Display the test script and breakpoints

g

Start/resume execution

s [N]

Execute N statements (default 1)

x

Toggle execution trace on/off

h, ?

Print the help menu

! string

Pass string to a shell

q

Quit
原文地址:https://www.cnblogs.com/winditsway/p/14543929.html