Shell调试纠错技巧
2016-10-01 15:16
134 查看
背景:
初次写技术博客,总体感觉还是比较不习惯,今天先拿Shell脚本作为主题写一篇技术博文。用了Shell也有一段时间了,我个人对于shell感觉就是用起来很顺手,但是一旦遇到shell出错,我觉得会非常的难以调试,今天准备罗列下一些相关的调试方法,方便自己作为笔记来记录自己的学习过程,也方便大家有个查阅和参考的地方。
正文:
shell这门语言古老而又优雅,我们看到Shell的书籍上一般都是以乌龟(应该是海龟)作为吉祥物,我觉得很是形象,首先Shell整个运行的效率不高,而且的确是一个很长寿的语言,然而就是这么一种语言,又是那么的坚不可摧(我们看看他的外壳就可以知道^_^)在正式开始讨论之前,我先放一篇脑图,围绕脑图中的概要,我们逐步展开细节讨论
1、Shell版本:
shell的版本众多,要想掌握所有版本的语法,可能会有一定难度,如下是网上一篇文章列出的shell版本:bash(这个使我们经常用的)
bash是Linux标准默认的shell,本教程也基于bash讲解。bash由Brian Fox和Chet Ramey共同完成,是BourneAgain Shell的缩写,内部命令一共有40个。Linux使用它作为默认的shell是因为它有诸如以下的特色
可以使用类似DOS下面的doskey的功能,用方向键查阅和快速输入并修改命令
自动通过查找匹配的方式给出以某字符串开头的命令。
包含了自身的帮助功能,你只要在提示符下面键入help就可以得到相关的帮助。
sh (这个也是)
sh 由Steve Bourne开发,是Bourne Shell的缩写,sh 是Unix 标准默认的shell。ash
ash shell 是由Kenneth Almquist编写的,Linux中占用系统资源最少的一个小shell,它只包含24个内部命令,因而使用起来很不方便。csh
csh 是Linux比较大的内核,它由以William Joy为代表的共计47位作者编成,共有52个内部命令。该shell其实是指向/bin/tcsh这样的一个shell,也就是说,csh其实就是tcsh。ksh
ksh 是Korn shell的缩写,由Eric Gisin编写,共有42条内部命令。该shell最大的优点是几乎和商业发行版的ksh完全兼容,这样就可以在不用花钱购买商业版本的情况下尝试商业版本的性能了。注意:bash是 Bourne Again Shell 的缩写,是linux标准的默认shell ,它基于Bourne shell,吸收了C shell和Korn shell的一些特性。bash完全兼容sh,也就是说,用sh写的脚本可以不加修改的在bash中执行。
其中对于Sh和Bash是我们比较常用的,其他的有兴趣可以自行去查阅下,不同的Shell解释器在语法上会有一些不一样的地方。
2、Shell排错:
Shell排错主要是针对shell怪异的语法,很多时间代码写完之后,仅凭肉眼无法看透错误的地方,甚至来说Shell解释器给出的错误提示也是极为简陋的,这里面我们先以一个小例子来说明这个问题,后面的相关调试方法也是基于这个例子展开。#!/bin/sh function trapError()<span style="font-family:Microsoft YaHei;"> //trap的例子</span> { trap 'trapError $LINENO $FUNCNAME' ERR echo "[LINE:$1] [FUNCNAME:$2] Error: Command or function exited with status $?" } function fool () { if [ $1 == 'true' ];then echo "I am foo" echo 1 fi echo 0 } function callFool() { echo "start call Fool" foo=`fool` echo "end call Fool" } if [ 1 -eq 1 ];then if [ 1 -eq 1 ];then echo "test" fi fi if [ "$DEBUG" == "DEBUG" ];then trap 'trapError $LINENO' ERR fi abc callFool echo 'end'针对上面这个示例代码,我觉得大多数人都很难一眼看到这里面的错误,所以这里我送给大家一个神器ShellCheck
1) ShellCheck
ShellCheck是一款检查Shell语法的神器,主要是对Shell脚本的风格和语法进行静态扫描,能快速定位到问题所在,同时也会规范自己写Shell代码的一款极具指导性的工具。ShellCheck分为在线版和离线版,分别是一个在线网站和一个安装到linux下的工具1) 在线版的ShellCheck:http://www.shellcheck.net/,使用方式很简单,可以自行尝试。
2) 离线版的ShellCheck,主要是在linux下安装一下(yum或者apt-get即能快速安装)。
我们简单地拿ShellCheck为例来走下全过程。
a) 在linux下安装ShellCheck
$ sudo apt-get install shellcheck
or
$ yum install shellcheck
b) shellcheck xxx.sh
如下是检查结果,黄色的属于一般警告,绿色的标示较为严重的警告,图中相关地方我已经加了标注。
可以看到里面静态分析还是很精准的,我建议一般的脚本都用shellcheck去扫描一下,这样不仅有助于自己写出好的shell代码,还能最快地揪出代码
中的一些小问题(也许这些问题单独去review代码要花上很长时间)。
2) Trap命令
trap是linux里面的一个处理信号的命令,关于信号是什么, 可以自行去网上搜索相关的文章,这里简单解释下就是我们按某些系统组合键,例如ctrl+c
一般是中断当前的程序,那么当我们按下这个组合键的时候,操作系统就会给程序发一个SIGINT的信号,程序接收到这个信号后就会执行默认的操作,即退出
当前的程序运行状态。
你可以使用kill -l查看系统中的信号
由此可以想到一个问题,Shell在执行期间如果运行出了问题导致shell脚本异常退出,这个时候是一个怎么样的机制呢?
聪明的人应该会想到,肯定信号在其中扮演了很重要的角色,下面我们说说Shell的伪信号。
信号名 | 何时产生 |
EXIT | 从一个函数中退出或整个脚本执行完毕 |
ERR | 当一条命令返回非零状态时(代表命令执行不成功) |
DEBUG | 脚本中每一条命令执行之前 |
通过捕获EXIT信号,我们可以在shell脚本中止执行或从函数中退出时,输出某些想要跟踪的变量的值,并由此来判断脚本的执行状态以及出错原因,其使用方法是:
trap 'command' EXIT 或 trap 'command' 0
通过捕获ERR信号,我们可以方便的追踪执行不成功的命令或函数,并输出相关的调试信息,以下是一个捕获ERR信号的示例程序,其中的$LINENO是一个shell的内置变量,代表shell脚本的当前行号。
通过捕获EXIT信号,我们可以在shell脚本中止执行或从函数中退出时,输出某些想要跟踪的变量的值,并由此来判断脚本的执行状态以及出错原因,其使用方法是:
trap 'command' EXIT 或 trap 'command' 0
通过捕获ERR信号,我们可以方便的追踪执行不成功的命令或函数,并输出相关的调试信息,以下是一个捕获ERR信号的示例程序,其中的$LINENO是一个shell的内置变量,代表shell脚本的当前行号。从 unix-EA博客引用
上面这段话主要是讲相关的理论,这里我们尝试以上面的例子为例来阐述如何使用该命令。
下面是脚本内容,相关地方已经标注
执行结果
上文中我们注意一个技巧,把所有调试的代码用DEBUG代码块包含起来,这样非常方便调试,其实这里不一定是DEBUG宏,可以是其他的宏,主要是依据惯例以DEBUG
去做这个开关。
3) set -x 命令
set -x 是一个比较常用的调试方式,我们先简单地分类描述相关的使用方式:直接sh -x scriptname,这样的会打印全局的执行细节,这种使用方式比较粗暴,适用于有一定编程经验的同学使用
在相关的代码段上下文加入set -x 和 set +x,那么这段内容的执行详情就会被打印出来
结合DEBUG宏和export PS4='+{$LINENO:${FUNCNAME[0]}} '来定位问题,这个是我们比较常用的一种方式,也是比较优雅的方式
这里面我们直接拿一个3的例子:)
4) bashdb
看到这个*db的命令,应该就知道是一个debug工具了,和gdb,pdb类似,这个是shell脚本的调试工具,不过应该主要是针对bashshell的脚本的调试工具,使用方法和gdb类似,下面直接举例吧
上面给出的这个例子里面的命令是我觉得比较常用的,其实一般来说可能也用不上这么犀利的工具,看个人爱好吧