Linux Shell脚本调试的四种办法

    Linux脚本调试

    前言bash自身特性-v-n-x-x的增强$LINENO$FUNCNAME$[PS4](https://blog.51cto.com/kusorz/1968827)

    trap调试钩子bashdb

    前言

    Linux的shell脚本上手简单但是调试起来就颇为麻烦,二者间的抉择就像编辑器上轻量级与功能丰富的不可兼得一般。在这里提出一些Linux上脚本调试的小技巧,希望有所帮助。

    bash自身特性

    众所周知,bash相对于dash而言是更重量级的存在,其中一部分便是这种调试辅助信息。以下的特性都是在bash上验证的,dash下无说明便是没有测试过。 了解一个命令的最好办法自然是man手册: 手册本身命令很多,这次只介绍几个我常用的参数:

    -v

    一边执行脚本,一边将执行过的脚本命令打印。 注意: 脚本命令本身是被定义到标准错误输出打印,而真实结果是在标准输出打印。

    -n

    读一遍脚本中的命令但不执行,用于检查脚本中的语法错误。 注意:这里的检查主要是针对语法格式缺少(例如,if语句少了then),但是还有很多是没有检查的(比如,if语句没加空格、函数未定义)。

    -x

    提供跟踪执行信息,将执行的每一条命令和结果依次打印出来。这个的功能超级强大,将会重点介绍。

    如果有多个脚本嵌套关系,可以进入次级脚本检查(比如a.sh调用b.sh,b.sh调用c.sh,只会显示a.sh和b.sh的内容)

    所以最好是哪个脚本想追踪就在哪个脚本里加上set -x。

    行首显示+,+后面显示经过替换之后的命令行内容,有助于分析实际执行的是什么命令。++代表函数或脚本进一步的调用。

    示例:

    root@hik-PC:/tmp# cat test.sh

    #!/bin/bash

    echo 111

    SER="/usr/lib/dconf/dconf-service"

    fun(){

    pp=`ps -ef | grep $SER`

    }

    fun

    bash a.sh

    root@hik-PC:/tmp# cat a.sh

    #!/bin/bash

    echo aaa

    dd=`ps -ef |grep a.sh`

    root@hik-PC:/tmp# bash -x test.sh

    + echo 111

    111

    + SER=/usr/lib/dconf/dconf-service

    + fun

    ++ ps -ef

    ++ grep /usr/lib/dconf/dconf-service

    + pp='hikvisi+ 3348 3016 0 5月13 ? 00:00:00 /usr/lib/dconf/dconf-service

    root 32422 32420 0 11:22 pts/1 00:00:00 grep /usr/lib/dconf/dconf-service'

    + bash a.sh

    aaa

    -x的增强

    $LINENO

    代表shell脚本的当前行号

    $FUNCNAME

    函数的名字,它是一个数组变量,其中包含了整个调用链上所有的函数的名字,所以变量${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字,而变量${FUNCNAME[1]}则代表调用函数${FUNCNAME[0]}的函数的名字,余者可以依此类推。

    $PS4

    $PS4的值将被显示在“-x”选项输出的每一条命令的前面,在Bash Shell中,缺省的$PS4的值是"+"号。所以默认执行bash -x时前面有“+”号。进一步介绍,可以参照上面我的参考文献。

    示例:

    root@hik-PC:/tmp# cat -n test.sh

    1 #!/bin/sh

    2 export PS4='+$LINENO: {${FUNCNAME[0]}}'

    3 echo 111

    4 SER="/usr/lib/dconf/dconf-service"

    5 fun(){

    6 pp=`ps -ef | grep $SER`

    7 }

    8 funn(){

    9 fun

    10 }

    11 funn

    12 bash a.sh

    root@hik-PC:/tmp# bash -x test.sh

    + export 'PS4=+$LINENO: {${FUNCNAME[0]}}'

    + PS4='+$LINENO: {${FUNCNAME[0]}}'

    +3: {}echo 111

    111

    +4: {}SER=/usr/lib/dconf/dconf-service

    +11: {}funn

    +9: {funn}fun

    ++6: {fun}ps -ef

    ++6: {fun}grep /usr/lib/dconf/dconf-service

    +6: {fun}pp='hikvisi+ 3348 3016 0 5月13 ? 00:00:00 /usr/lib/dconf/dconf-service

    root 5476 5474 0 13:55 pts/1 00:00:00 grep /usr/lib/dconf/dconf-service'

    +12: {}bash a.sh

    aaa

    最后,这些命令有多种实现方式:

    命令行:bash -x xx.sh在脚本开头提供参数#!/bin/bash -x脚本中,set -x (启用) / set +x (禁用)

    trap

    定义:用于捕获指定的信号并执行预定义的命令。 语法:trap ‘command’ signal 注意:

    signal主要使用shell内部的伪信号(exit,err,debug debug是脚本中每一条命令执行之前),很多资料也写可以捕获系统信号,但没有测试过。dash没有这种伪信号,所以必须用bash。

    示例:

    root@hik-PC:/tmp# cat -n trap.sh

    1 #!/bin/bash

    2 trap 'echo “before execute line:$LINENO, a=$a,b=$b,c=$c”' DEBUG

    3 a=1

    4 if [ "$a" -eq 1 ]

    5 then

    6 b=2

    7 else

    8 b=1

    9 fi

    10 c=3

    11 echo "end"

    root@hik-PC:/tmp# bash trap.sh

    “before execute line:3, a=,b=,c=”

    “before execute line:4, a=1,b=,c=”

    “before execute line:6, a=1,b=,c=”

    “before execute line:10, a=1,b=2,c=”

    “before execute line:11, a=1,b=2,c=3”

    end

    调试钩子

    用途:当测试环境和部署环境不同时,不同的要求和判定能把人折磨疯,利用export DEBUG=true既可以执行判断语句内内容,不用一条条删语句了。 示例:

    if [ “$DEBUG” = “true” ]; then

    echo “debugging” #此处可以输出调试信息

    fi

    bashdb

    bashdb是一个类GDB的调试工具,可以完成对shell脚本的断点设置,单步执行,变量观察等许多功能。

    使用bashdb进行debug的常用命令:

    1. 列出代码和查询代码类:

    l 列出当前行以下的10行

    \- 列出正在执行的代码行的前面10行

    . 回到正在执行的代码行

    w 列出正在执行的代码行前后的代码

    /pat/ 向后搜索pat

    ?pat?向前搜索pat

    2. Debug控制类:

    h 帮助

    help 命令 得到命令的具体信息

    q 退出bashdb

    x 算数表达式 计算算数表达式的值,并显示出来

    !!空格Shell命令 参数 执行shell命令

    使用bashdb进行debug的常用命令(cont.)

    控制脚本执行类:

    n 执行下一条语句,遇到函数,不进入函数里面执行,将函数当作黑盒

    s n 单步执行n次,遇到函数进入函数里面

    b 行号n 在行号n处设置断点

    (经验证,bashdb的break设置断点命令必须s、s、c然后到这个断点以后,还得重新设置下一个断点,否则不生效,===>即再次s、s、c才行)

    del 行号n 撤销行号n处的断点

    c 行号n 一直执行到行号n处

    R 重新启动

    Finish 执行到程序最后

    cond n expr 条件断点