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