shell常用的debug方法

shell脚本中输出调试信息常用的如下方式:
1.trap命令
    trap命令用于捕获指定的信号并执行预定义的命令。其基本的语法是:trap ‘command‘ signal。其中,signal是要捕获的信号,command是捕获到指定的信号之后,要执行的命令。可以用kill -l命令查看系统中全部可用的信号名,
    捕获后所执行的命令可以是任何一条或多条合法的shell语句,也可以是一个函数名。shell在脚本执行时,会产生三个所谓的"伪信号"(只所以称为伪信号,是因为这三个信号有shell产生,而其他的信号是由操作系统产生的),通过
    使用trap命令捕获这三个"伪信号"并输出相关信息。shell脚本在执行时产生的"伪信号"如下:
        EXIT   从一个函数中退出或者整个脚本执行完产生的信号
        ERR    当一条命令返回非零状态时(代表命令执行不成功)产生的信号
        DEBUG  脚本中每一条命令执行前产生的信号
    通过捕获EXIT信号,我们可以在shell脚本中止执行或从函数中退出时,输出某些想要跟踪的变量的值,并由此来判断脚本的执行状态以及出错原因,其使用方法是:trap ‘command‘ EXIT 或 trap ‘command‘ 0;通过捕获ERR信号,我们可以方便的追踪执行不成功的命令或函数,并输出相关的调试信息,以下是一个捕获ERR信号的示例程序,其中的$LINENO是一个shell的内置变量,代表shell脚本的当前行号;过捕获DEBUG信号,我们只需要一条trap语句就可以完成对相关变量的全程跟踪。
    例如:
        1.
            ERRORTRAP(){
                echo "LINE:$1 Error:command or function exited with status $?"
            }
            test(){
                echo 1111111;
            }
            trap ‘ERRORTRAP $LINENO‘ ERR
            sdfasdf    #随便写的命令
            foo
        2.
           trap ‘echo “before execute line:$LINENO, a=$a,b=$b,c=$c”‘ DEBUG
           a=1
           if [ "$a" -eq 1 ]
           then
              b=2
           else
              b=1
           fi
           c=3
           echo "end"
           #使用DEBUG可以清晰的看到每执行一条命令之后,相关变量的值的变化。同时,从运行结果中打印出来的行号来分析,可以看到整个脚本的执行轨迹,能够判断出哪些条件分支执行了,哪些条件分支没有执行。
tee命令
    在shell脚本中管道以及输入输出重定向使用非常多,通过管道一些命令的执行结果直接成为了下一条命令的输入。如果出现错误,就需要逐步检查各条命令的执行结果来判断问题出现在哪儿,但是因为使用了管道,这些中间结果并不会显示在屏幕上,给调试带来困难,这个时候就可以借助tee命令。tee命令会从标准输入读取数据,将其内容输出到标准输出设备,同时又可将内容保存文件。
    例如:
        1.
            /sbin/ifconfig | grep ‘inet addr:‘ | grep -v ‘127.0.0.1‘ | tee temp.txt | cut -d : -f3 | awk ‘{print $1}‘
            #然后可以通过查看temp.txt文件的内容来查看输出的内容
使用"调试钩子"
    在C语言程序中,我们经常使用DEBUG宏来控制是否要输出调试信息,在shell脚本中也可以使用这样的机制,如下列代码所示:
    if [ “$DEBUG” = “true” ]; then
        echo “debugging”  #此处可以输出调试信息
    fi
    这样的代码块通常称之为“调试钩子”或“调试块”。在调试钩子内部可以输出任何您想输出的调试信息,使用调试钩子的好处是它是可以通过DEBUG变量来控制的,在脚本的开发调试阶段,可以先执行export DEBUG=true命令打开调试钩子,使其输出调试信息。
    例如:
        1.
           DEBUG="true"  #来控制是否输出调试信息
           DEBUG(){
               if [ "$DEBUG" = "true" ]; then
                   $@  
               fi
           }
           a=1
           DEBUG echo "a=$a"
           if [ "$a" -eq 1 ]
           then
                b=2
           else
                b=1
           fi
           DEBUG echo "b=$b"
           c=3
           DEBUG echo "c=$c"
使用shell的执行选项
    以上三种调试手段是通过修改shell脚本的源代码,来输出相应的调试信息来定位错误。现在介绍一些不用修改源代码来调试shell脚本的方法。
    -n
        只读取shell脚本,但不实际执行,可用于测试shell脚本是否存在语法错误。良好的习惯就是在编写完成shell脚本之后使用-n来测试脚本是否有
        语法错误。
    -x
        进入跟踪方式,显示所执行的每一条命令,该选项使shell在执行脚本的过程中把它实际执行的每一个命令行显示出来,并且在行首显示一个"+"号。 "+"号后面显示的是经过了变量替换之后的命令行的内容,有助于分析实际执行的是什么命令。
    -c
        "string" 从strings中读取命令,使shell解释器从一个字符串中而不是从一个文件中读取并执行shell命令。当需要临时测试一小段脚本的执行结果时,
        可以使用这个选项,如下所示:
            sh -c ‘a=1;b=2;let c=$a+$b;echo "c=$c"‘
    shell的执行选项除了可以在启动shell时指定外,亦可在脚本中用set命令来指定。 "set -参数"表示启用某选项,"set +参数"表示关闭某选项。有时候我们并不需要在启动时用"-x"选项来跟踪所有的命令行,这时我们可以在脚本中使用set命令。
    例如:
        1.
             1 #!/bin/bash
               isRoot() {
                       if [ "$UID" -ne 0 ]
                               return 1
                       else
                               return 0
                       fi
               }
               isRoot
               if ["$?" -ne 0 ]
               then
                       echo "Must be root to run this script"
                       exit 1
               else
                       echo "welcome root user"
                       #do something
               fi
            #首先使用sh -n test.sh检查是否有语法错误。如果有就该;然后,sh test.sh执行脚本,如果有错误就用如下的方式调试:
              export PS4=‘+{$LINENO:${FUNCNAME[0]}}‘
              sh -x test.sh
              其中,
              $LINENO:
                  代表shell脚本的当前行号,类似于C语言中的内置宏__LINE__;
              $FUNCNAME:
                  函数的名字,类似于C语言中的内置宏__func__,但宏__func__只能代表当前所在的函数名,而$FUNCNAME的功能更强大,它是一个数组变量,
                  其中包含了整个调用链上所有的函数的名字,故变量${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字,而变量${FUNCNAME[1]}则
                  代表调用函数${FUNCNAME[0]}的函数的名字,余者可以依此类推;
              $PS4:
                  主提示符变量$PS1和第二级提示符变量$PS2比较常见,但很少有人注意到第四级提示符变量$PS4的作用。我们知道使用“-x”执行选项将会
                  显示shell脚本中每一条实际执行过的命令,而$PS4的值将被显示在“-x”选项输出的每一条命令的前面。在Bash Shell中,缺省的$PS4的值
                  是"+"号。(现在知道为什么使用"-x"选项时,输出的命令前面有一个"+"号了吧?);
              利用$PS4这一特性,通过使用一些内置变量来重定义$PS4的值,我们就可以增强"-x"选项的输出信息。例如先执行export PS4=‘+{$LINENO:${FUNCNAME[0]}} ‘, 然后再使用“-x”选项来执行脚本,就能在每一条实际执行的命令前面显示其行号以及所属的函数名。


总结

    首先使用“-n”选项检查语法错误,然后使用“-x”选项跟踪脚本的执行,使用“-x”选项之前,别忘了先定制PS4变量的值来增强“-x”选项的输出信息,至少应该令其输出行号信息(先执行export PS4=‘+[$LINENO]‘,更一劳永逸的办法是将这条语句加到您用户主目录的.bash_profile文件中去),这将使你的调试之旅更轻松。也可以利用trap,调试钩子等手段输出关键调试信息,快速缩小排查错误的范围,并在脚本中使用“set -x”及“set +x”对某些代码块进行重点跟踪。这样多种手段齐下,相信您已经可以比较轻松地抓出您的shell脚本中的臭虫了。如果您的脚本足够复杂,还需要更强的调试能力,可以使用shell调试器bashdb,这是一个类似于GDB的调试工具,可以完成对shell脚本的断点设置,单步执行,变量观察等许多功能。

参考资料:

  http://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。