`

使用trap/tee命令/调试钩子调试Shell脚本

 
阅读更多
本文全面系统地介绍了shell脚本调试技术,包括使用echo, tee, trap等命令输出关键信息,跟踪变量的值,在脚本中植入调试钩子,使用“-n”选项进行shell脚本的语法检查,使用“-x”选项实现shell脚本逐条语句的跟踪,巧妙地利用shell的内置变量增强“-x”选项的输出信息等。
一. 前言
我们之所以要进行Shell脚本调试,就是为了发现引发脚本错误的原因以及在脚本源代码中定位发生错误的行,常用的手段包括分析输出的错误信息,在脚本中加入调试语句,输出调试信息来辅助诊断错误,利用调试工具等。但与其它高级语言(java,c++等)相比,shell解释器缺乏相应的调试机制和调试工具的支持,其输出的错误信息又往往很不明确,初学者在调试脚本时,除了知道用echo语句输出一些信息外,别无它法,而仅仅依赖于大量的加入echo语句来诊断错误,确实令人不胜其繁,故常见初学者抱怨shell脚本太难调试了。
现在我们系统地学习一些重要的shell脚本调试技术,相信对很多shell的初学者有很大帮助的。
阅读下面内容前,要求读者具有基本的shell编程知识。本文实验平台是Bash3.1+Redhat Enterprise Server 4.0,但所介绍的调试技巧应该也同样适用于其它shell和Unix平台。
二. 在shell脚本中输出调试信息
通过在程序中加入调试语句把一些关键地方或出错的地方的相关信息显示出来。
Shell程序员通常使用echo(ksh程序员常使用print)语句输出信息,但仅仅依靠echo语句的输出跟踪信息太麻烦,调试阶段在脚本中加入的大量的echo语句在产品成形时还得再一一删除掉。这样,这里来说下如何方便有效的输出调试信息。
1. 使用trap命令
trap命令用于捕获指定的信号并执行预定义的命令。其基本的语法是:
trap 'command' signal
其中signal是要捕获的信号,command是捕获到指定的信号之后,所要执行的命令。
我们可以用kill –l命令看到系统中全部可用的信号名,捕获信号后所执行的命令可以是任何一条或多条合法的shell语句,也可以是一个函数名。shell脚本在执行时,会产生三个所谓的“伪信号”(之所以称之为“伪信号”是因为这三个信号是由shell产生的,而其它的信号是由操作系统产生的),通过使用trap命令捕获这三个“伪信号”并输出相关信息对调试非常有帮助。
表 1. shell伪信号
信号名何时产生
EXIT从一个函数中退出或整个脚本执行完毕
ERR当一条命令返回非零状态时(代表命令执行不成功)
DEBUG脚本中每一条命令执行之前
通过捕获EXIT信号,我们可以在shell脚本中止执行或从函数中退出时,输出某些想要跟踪的变量的值,并由此来判断脚本的执行状态以及出错原因,其使用方法是:
trap 'command' EXIT 或者 trap 'command' 0
通过捕获ERR信号,我们可以方便的追踪执行不成功的命令或函数,并输出相关的调试信息,以下是一个捕获ERR信号的示例程序,其中的$LINENO是一个shell的内置变量,代表shell脚本的当前行号。
举例:
$ cat -n exp1.sh
1ERRTRAP()
2{
3 echo "[LINE:$1] Error: Command or function exited with status $?"
4}
5foo()
6{
7 return 1;
8}
9trap 'ERRTRAP $LINENO' ERR
10abc
11foo
其输出结果如下:
$ sh exp1.sh
exp1.sh: line 10: abc: command not found
[LINE:10] Error: Command or function exited with status 127
[LINE:11] Error: Command or function exited with status 1
在调试过程中,为了跟踪某些变量的值,我们常常需要在shell脚本的许多地方插入相同的echo语句来打印相关变量的值,这种做法显得烦琐而笨拙。而通过捕获DEBUG信号,我们只需要一条trap语句就可以完成对相关变量的全程跟踪。
下面是一个通过捕获DEBUG信号来跟踪变量的示例程序:
$ cat –n exp2.sh
1#!/bin/bash
2trap 'echo “before execute line:$LINENO, a=$a,b=$b,c=$c”' DEBUG
3a=1
4if [ "$a" -eq 1 ]
5then
6 b=2
7else
8 b=1
9fi
10c=3
11echo "end"
其输出结果如下:
$ sh exp2.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
从运行结果中我们可以清晰的看到每执行一条命令之后,相关变量的值的变化。同时,从运行结果中打印出来的行号来分析,可以看到整个脚本的执行轨迹,能够判断出哪些条件分支执行了,哪些条件分支没有执行。
2. 使用tee命令
在shell脚本中管道以及输入输出重定向使用得非常多,在管道的作用下,一些命令的执行结果直接成为了下一条命令的输入。如果我们发现由管道连接起来的一批命令的执行结果并非如预期的那样,就需要逐步检查各条命令的执行结果来判断问题出在哪儿,但因为使用了管道,这些中间结果并不会显示在屏幕上,给调试带来了困难,这时我们就可以借助于tee命令了。
tee命令会从标准输入读取数据,将其内容输出到标准输出设备,同时又可将内容保存成文件。例如有如下的脚本片段,其作用是获取本机的ip地址:
ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| cut -d : -f3 | awk '{print $1}'`
#注意=号后面的整句是用反引号(数字1键的左边那个键)括起来的。
echo $ipaddr
运行这个脚本,实际输出的却不是本机的ip地址,而是广播地址,这时我们可以借助tee命令,输出某些中间结果,将上述脚本片段修改为:
ipaddr=`/sbin/ifconfig | grep 'inet addr:' | grep -v '127.0.0.1'
| tee temp.txt | cut -d : -f3 | awk '{print $1}'`
echo $ipaddr
之后,将这段脚本再执行一遍,然后查看temp.txt文件的内容:
$ cat temp.txt
inet addr:192.168.0.1Bcast:192.168.0.255Mask:255.255.255.0
我们可以发现中间结果的第二列(列之间以:号分隔)才包含了IP地址,而在上面的脚本中使用cut命令截取了第三列,所以我们只需将脚本中的cut -d : -f3改为cut -d : -f2即可得到正确的结果。
具体到上述的脚本例子,我们也许并不需要tee命令的帮助,比如我们可以分段执行由管道连接起来的各条命令并查看各命令的输出结果来诊断错误,但在一些复杂的shell脚本中,这些由管道连接起来的命令可能又依赖于脚本中定义的一些其它变量,这时我们想要在提示符下来分段运行各条命令就会非常麻烦了,简单地在管道之间插入一条tee命令来查看中间结果会更方便一些。
3. 使用"调试钩子"
学过C语言程序的朋友有这个印象吗:我们经常使用DEBUG宏来控制是否要输出调试信息,在shell脚本中我们同样可以使用这样的机制,如下列代码所示:
if [ “$DEBUG” = “true” ]; then
echo “debugging”#此处可以输出调试信息
fi
这样的代码块通常称之为“调试钩子”或“调试块”。在调试钩子内部可以输出任何您想输出的调试信息,使用调试钩子的好处是它是可以通过DEBUG变量来控制的,在脚本的开发调试阶段,可以先执行export DEBUG=true命令打开调试钩子,使其输出调试信息,而在把脚本交付使用时,也无需再费事把脚本中的调试语句一一删除。
如果在每一处需要输出调试信息的地方均使用if语句来判断DEBUG变量的值,还是显得比较繁琐,通过定义一个DEBUG函数可以使植入调试钩子的过程更简洁方便,如下面代码所示:
$ cat –n exp3.sh
1DEBUG()
2{
3if [ "$DEBUG" = "true" ]; then
4 $@  
5fi
6}
7a=1
8DEBUG echo "a=$a"
9if [ "$a" -eq 1 ]
10then
11 b=2
12else
13 b=1
14fi
15DEBUG echo "b=$b"
16c=3
17DEBUG echo "c=$c"
在上面所示的DEBUG函数中,会执行任何传给它的命令,并且这个执行过程是可以通过DEBUG变量的值来控制的,我们可以把所有跟调试有关的命令都作为DEBUG函数的参数来调用,非常的方便。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/77896/showart_1160697.html
分享到:
评论

相关推荐

    Shell脚本调试技术

    1. 使用trap命令 2.使用tee命令 3.使用"调试钩子" 4.使用shell的执行选项 5.对"-x"选项的增强

    Linux Shell调试技术

    详细的分析了shell脚本所用的各种调试办法,对于初学者和需要熟练掌握shell的人都有很大的帮助。

    跟老男孩学Linux运维:Shell编程实战

    / 249第13章 Shell数组的应用实践 / 260第14章 Shell脚本开发规范 / 279第15章 Shell脚本的调试 / 286第16章 Shell脚本开发环境的配置和优化实践 / 297第17章 Linux信号及trap命令的企业应用实践 / 310第18章 ...

    snmp trap命令接收软件

    windows平台下,用于接收snmp协议的trap消息的小软件,可以测试你编写的trap命令是否发送成功了

    新版Linux Shell编程实训(全)20170518.docx

    任务12.3 使用“调试钩子” 193 任务12.4 使用Shell的执行选项 194 练习(每题50分,共计100分) 196 项目十三 Shell综合案例一(lnmp+wordpress) 197 [学习目标] 197 任务13.1 手动安装Linux Nginx Mysql PHP和...

    Linux shell脚本 精华中文版

    026_创建以日期命名的文件和临时文件_信号_trap命令以及如何捕获信号_eval命令_logger命令.pdf 027_脚本例子_pingall_backup_gen_del.lines_acces_deny_logroll_nfsdown.pdf 028_rcN.d_运行级别脚本编辑.pdf 029...

    Linux系统下trap命令在Shell编程中的应用.pdf

    Linux系统下trap命令在Shell编程中的应用.pdf

    shell编程和unix命令

    3.2.1 使用at命令提交命令或脚本 26 3.2.2 列出所提交的作业 27 3.2.3 清除一个作业 27 3.3 &命令 27 3.3.1 向后台提交命令 28 3.3.2 用ps命令查看进程 28 3.3.3 杀死后台进程 28 3.4 nohup命令 29 3.4.1 使用nohup...

    shell 编程指南pdf

    3.2.1 使用at命令提交命令或脚本 26 3.2.2 列出所提交的作业 27 3.2.3 清除一个作业 27 3.3 &命令 27 3.3.1 向后台提交命令 28 3.3.2 用ps命令查看进程 28 3.3.3 杀死后台进程 28 3.4 nohup命令 29 3.4.1 使用nohup...

    trapCommand

    rap是一个shell内建命令,它用来在脚本中指定...trap命令不仅仅处理Linux信号,还能对脚本退出(EXIT)、调试(DEBUG)、错误(ERR)、返回(RETURN)等情况指定处理方式。 常用参数 trap [-lp] [[arg] sigspec ...]

    PowerShell脚本trap语句捕获异常写法实例

     ‘在trap中捕获到脚本异常’  $_.Exception.Message  continue } .\3.three.test.ps1 异常捕获成功,输出: 代码如下:在trap中捕获到脚本异常 The term ‘Get-FanBingbing’ is not recognized as the name of ...

    SNMP_TRAP监听工具

    监听SNMPtrap消息的小工具,自带解析功能

    LINUX与UNIX SHELL编程指南(很全)

    3.2.1 使用at命令提交命令或脚本 26 3.2.2 列出所提交的作业 27 3.2.3 清除一个作业 27 3.3 &命令 27 3.3.1 向后台提交命令 28 3.3.2 用ps命令查看进程 28 3.3.3 杀死后台进程 28 3.4 nohup命令 29 3.4.1 使用nohup...

    绝版经典《Linux与UNIX Shell编程指南》

    3.2.1 使用at命令提交命令或脚本 26 3.2.2 列出所提交的作业 27 3.2.3 清除一个作业 27 3.3 &命令 27 3.3.1 向后台提交命令 28 3.3.2 用ps命令查看进程 28 3.3.3 杀死后台进程 28 3.4 nohup命令 29 3.4.1 使用...

    shell教程-30章,下了之后会让你大吃一惊,相当好

    3.2.1 使用at命令提交命令或脚本 26 3.2.2 列出所提交的作业 27 3.2.3 清除一个作业 27 3.3 &命令 27 3.3.1 向后台提交命令 28 3.3.2 用ps命令查看进程 28 3.3.3 杀死后台进程 28 3.4 nohup命令 29 3.4.1 使用nohup...

    LINUX与UNIX SHELL编程指南 高清PDF

    3.2.1 使用at命令提交命令或脚本 26 3.2.2 列出所提交的作业 27 3.2.3 清除一个作业 27 3.3 &命令 27 3.3.1 向后台提交命令 28 3.3.2 用ps命令查看进程 28 3.3.3 杀死后台进程 28 3.4 nohup命令 29 3.4.1 使用nohup...

    shell 编程(中文)[pdf]

    026_创建以日期命名的文件和临时文件_信号_trap命令以及如何捕获信号_eval命令_logger命令.pdf 027_脚本例子_pingall_backup_gen_del.lines_acces_deny_logroll_nfsdown.pdf 028_rcN.d_运行级别脚本编辑.pdf 029_cgi...

    Linux与Unix Shell编程指南(PDF格式,共30章)

    3.2.1 使用at命令提交命令或脚本 26 3.2.2 列出所提交的作业 27 3.2.3 清除一个作业 27 3.3 &命令 27 3.3.1 向后台提交命令 28 3.3.2 用ps命令查看进程 28 3.3.3 杀死后台进程 28 3.4 nohup命令 29 3.4.1 使用nohup...

Global site tag (gtag.js) - Google Analytics