zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    iOS 11 application 新特性
    Swift循环遍历集合方法
    Swift 使用 #warning
    swift 3.0 正则表达式查找/替换字符
    App Store 审核指南
    iOS 获取设备的各种信息的方法
    闭包(Closure)
    Swift的Guard语句
    Swift 学习指引
    Swift 4.0 废弃的柯里化
  • 原文地址:https://www.cnblogs.com/winditsway/p/14543929.html
Copyright © 2011-2022 走看看