`
无心出岫
  • 浏览: 1372 次
  • 性别: Icon_minigender_1
  • 来自: 西安
最近访客 更多访客>>
社区版块
存档分类
最新评论

Shell调试技术总结

阅读更多

Shell调试技术总结

学习没种语言都会在编写程序中遇到错误,当然shell也不例外。Shell脚本的错误主要可分为两类:第一类是shell脚本中的语法错误,脚本无法执行到底,第二类是shell脚本能够执行完毕,但并不能按照我们预期的结果那样,也就是存在逻辑错误。

第一类错误主要包括漏掉关键字、漏掉引号、空格符该有而未有、变量大小写没去分等。这一类错误一般通过自己的仔细检查都能够检查出来,关键是第二种错误。

在逻辑错误中Shell有几种调试方法。常见的Shell调试方法有traptee、调试钩子和Shell选项。

(一)trapLinux的内建命令,不要用于捕捉信号,trap命令可以指定收集某种信号所执行的命令,基本形式为:

trap command sign1 sign2 、、、signN

对于上述命令的解释为当trap收到sign1 sign2 、、、signN 中的任意一个信号时就执行command命令,当command命令执行完后脚本继续收到信号进行操作,直到脚本执行结束。

Shell脚本在执行时,会产生三个“伪信号”(原因是这三个信号是有Shell产生的,而其他信号是由操作系统产生的),利用trap命令可以捕捉到这三个“伪信号”并输出相关信息是Shell调试的一种重要技巧。三种“伪信号”分别为EXITERRDEBUG

     Shell伪信号即产生的条件

信号名称

 产生条件

EXIT

从函数中推出,或从整个脚本中退出

ERR

当一条命令返回非零状态码,即命令执行不成功

DEBUG

脚本中的每条命令执行之前

举例:

 

  1. 1 #!/bin/sh
  2. 2 fun()
  3. 3 {
  4. 4 echo "This is an correct function"
  5. 5 var=2011
  6. 6 return 0
  7. 7 }
  8. 8 trap 'echo "Line is $LINENO ,var=$var "' EXIT
  9. 9 fun
  10. [root@localhost Shell]# sh trap1
  11. This is an correct function
  12. Line is 1 ,var=2011

 

这个脚本先执行fun函数然后trap捕捉到EXIT信号后的输出,而EXIT信号是在fun函数执行完毕才产生的。 

  1. 1 #!/bin/sh
  2.   2 fun()
  3.   3 {
  4.   4 echo "This is an error function"
  5.   5 var=2011
  6.   6 return 1
  7.   7 }
  8.   8 trap 'echo "Line is $LINENO ,var=$var "' ERR
  9.   9 fun
  10.  10 ipconfig
  11. [root@localhost shell]# sh trap2
  12. This is an error function
  13. Line is 6 ,var=2011
  14. trap2: line 10: ipconfig: command not found
  15. Line is 10 ,var=2011

执行该脚本后先执行函数fun,该函数返回1,非零返回值的函数被认为是异常信号,然后trap捕捉到ERR信号然后输出行号和var的值,然后执行ipconfigipconfig不存在返回1,因此产生ERR信号故输出如上结果。

 

  1. 1 #!/bin/sh
  2. 2
  3. 3 trap 'echo "before execute line: $LINENO a=$a,b=$b,c=$c" ' DEBUG
  4. 4 a=6
  5. 5 b=2
  6. 6 c=30
  7. 7 while :
  8. 8 do
  9. 9 if (( $a >= 10 ))
  10. 10   then break
  11. 11 fi
  12. 12   let "a=$a+2"
  13. 13   let "b=$b*2"
  14. 14     let "c=$c-10"
  15. 15 done
  16. [root@localhost shell]# sh trapdebug
  17. before execute line: 4 a=,b=,c=
  18. before execute line: 5 a=6,b=,c=
  19. before execute line: 6 a=6,b=2,c=
  20. before execute line: 7 a=6,b=2,c=30
  21. before execute line: 9 a=6,b=2,c=30
  22. before execute line: 12 a=6,b=2,c=30
  23. before execute line: 13 a=8,b=2,c=30
  24. before execute line: 14 a=8,b=4,c=30
  25. before execute line: 7 a=8,b=4,c=20
  26. before execute line: 9 a=8,b=4,c=20
  27. before execute line: 12 a=8,b=4,c=20
  28. before execute line: 13 a=10,b=4,c=20
  29. before execute line: 14 a=10,b=8,c=20
  30. before execute line: 7 a=10,b=8,c=10
  31. before execute line: 9 a=10,b=8,c=10
  32. before execute line: 10 a=10,b=8,c=10
  33. before execute line: 16 a=10,b=8,c=10

 

在此脚本中先执行trapDEBUG然后接着不断的进行跟踪,通过上面运算可以分析到整个脚本的执行踪迹,能够判断吃哪些条件执行了哪些条件没有执行。

 trap命令通过捕捉三个“伪信号”能够方便的跟踪异常函数和信号,尽管echo也能实现,但是用trap更加高效、简洁。

 

(二)用tee命令进行Shell调试。tee命令产生的数据流分为两个,其中将一个输出到标准输出,一个到输出文件。利用tee的这种特性就可以逐步检查各条命令的执行结果来定位错误。tee还有一个选项-a,表示将Shell命令的输出追加到某文件的末尾。

 

  1. [root@localhost shell]# who |tee output
  2. cherish tty1 Oct 14 11:06 (:0)
  3. cherish pts/0 Oct 14 11:07 (:0.0)
  4. [root@localhost shell]# cat output
  5. cherish tty1 Oct 14 11:06 (:0)
  6. cherish pts/0 Oct 14 11:07 (:0.0)

 

先将who执行的结果从管道传给teetee命令将标准输出复制到文件output。因此仍然输出who命令标准输出。

 

  1. [root@localhost shell]# ps | tee -a output
  2. PID TTY TIME CMD
  3. 6928 pts/0 00:00:00 su
  4. 6932 pts/0 00:00:01 bash
  5. 29242 pts/0 00:00:00 ps
  6. 29243 pts/0 00:00:00 tee
  7. [root@localhost shell]# cat output
  8. cherish tty1 Oct 14 11:06 (:0)
  9. cherish pts/0 Oct 14 11:07 (:0.0)
  10. PID TTY TIME CMD
  11. 6928 pts/0 00:00:00 su
  12. 6932 pts/0 00:00:01 bash
  13. 29242 pts/0 00:00:00 ps
  14. 29243 pts/0 00:00:00 tee

 

将执行结果追加到output文件后边。

 

  1. 1 #!/bin/sh
  2. 2 localIP=`cat /etc/sysconfig/network-scripts/ifcfg-eth0 | tee dubug.txt | grep 'IPADDR' | tee -a dubug.txt | cut -d= -f2 | tee -a dubug.txt`
  3. 3 echo "the local ip is :$localIP"
  4. [root@localhost shell]# sh tee1
  5. the local ip is :192.168.1.109
  6. [root@localhost shell]# cat dubug.txt
  7. DEVICE=eth0
  8. BOOTPROTO=none
  9. NM_CONTROLLED=yes
  10. ONBOOT=yes
  11. HWADDR=00:0c:29:57:0d:1f
  12. MTU=1500
  13. TYPE=Ethernet
  14. DEFROUTE=yes
  15. PEERROUTES=yes
  16. IPV4_FAILURE_FATAL=yes
  17. IPV6INIT=no
  18. NAME="System eth0"
  19. UUID=5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03
  20. IPADDR=192.168.1.109
  21. NETMASK=255.255.255.0
  22. GATEWAY=192.168.1.1
  23. DNS1=8.8.8.8
  24. USERCTL=no
  25. IPADDR=192.168.1.109
  26. 192.168.1.109

 

该脚本中先找出ifcfg-eth0文件内容,然后在dubug.txt后边追加IPADDR内容,最后追加ip地址,从debug.txt可以清晰看出数据的流向。便于发现脚本中存在的逻辑错误。

(三)调试钩子,也称调试块,是源于其他的高级程序语言的调试方法,调试钩子实际上就是一个if/then结构的代码块,DEBUG变量控制该代码是否执行,在程序的开发调试阶段将DEBUG变量设置为TRUE,使其输出调试信息,到了程序交付阶段,将DEBUG设置为FALSE,关闭调试钩子,而无需一一删除调试钩子代码。一般调试钩子就是如下代码块:

 

  1. 1 if [ "$DEBUG" = "true" ]
  2. 2 then
  3. 3 echo "Debugging information: "
  4. 4 、、、、
  5. 5 fi

 

调试钩子中DEBUG是一个全局变量,在开始调试阶段可利用export DEBUG=true DEBUG设置为true 。当然上面的代码块在需要许多地方需要调试的情况下非常麻烦,我以我们可以定义一个DEBUG函数使植入调试钩子,这样比较方便。

 

  1. 1 #!/bin/sh
  2. 2 DEBUG()
  3. 3 {
  4. 4 if [ "$DEBUG" = "true" ]
  5. 5 then
  6. 6 $@
  7. 7 fi
  8. 8 }
  9. 9 a=0
  10. 10 b=2
  11. 11 c=100
  12. 12 DEBUG echo "a=$a b=$b c =$c" #第1个调试钩子
  13. 13 while :
  14. 14 do
  15. 15 DEBUG echo "a=$a b=$b c =$c" #第个调试钩子
  16. 16 if (( $a >= 10 ))
  17. 17 then
  18. 18 break
  19. 19 fi
  20. 20 let "a=$a+2"
  21. 21 let "b=$b*2"
  22. 22 let "c=$c-10"
  23. 23 done
  24. [root@localhost shell]# export DEBUG=true
  25. [root@localhost shell]# sh debugblock
  26. a=0 b=2 c =100
  27. a=0 b=2 c =100
  28. a=2 b=4 c =90
  29. a=4 b=8 c =80
  30. a=6 b=16 c =70
  31. a=8 b=32 c =60
  32. a=10 b=64 c =50

 

调试钩子是一个if/else结构。当DEBUG变量为true时,执行所有的位置函数。对于上述脚本先用export命令将DEBUG赋值为true,调试钩子启动执行脚本时不断输出abc三个变量的值,达到最终跟踪变量值变化的目的。这种方法与trap命令捕捉DEBUG信号跟踪变量值方法是等价的。

(四)使用Shell选项。

使用Shelll选项的调试方法是一种不修改源代码的一种方法。在众多Shell选项中,有三个选项可以用于脚本的调试它们是-n-x、和-c

Shell的调试选项、简写和意义

选项名称

简写

意义

noexec

n

读取脚本中的命令,进行语法检查,但不执行命令

xtrace

x

在执行每个命令之前,将每个命令打印到标准输出(stdout

c..

..中读取命令

     举例

 

  1. 1 #!/bin/bash
  2. 2 set -x
  3. 3 export PATH
  4. 4 read -p "Please input (Y/N)"
  5. 5 if [ "$yn" == "$Y" ] || [ "$yn" == "$y"]; then
  6. 6 echo "ok ,continue";
  7. 7 elif [ "$yn" == "$N" ] || [ "$yn" == "$n"]; then
  8. 8 echo "oh ,interrupt!";
  9. 9 else echo "I don't know what is your choise"
  10. 10 fi
  11. [root@localhost shell]# sh else
  12. + export PATH
  13. + read -p 'Please input (Y/N)'
  14. Please input (Y/N)y
  15. + '[' '' == '' ']'
  16. + echo 'ok ,continue'
  17. ok ,continue

 

如果脚本中加入了set –x,那么在set命令之后执行的命令以及加在命令行中的任何参数(包括变量和变量的值)都会显示出来。在一行之前都会加上加号(+),提示它是跟踪输出的标致。在子Shell中执行的shell跟踪命令会在一行前面加上两个加号即“++“。

set -参数"表示启用某选项,"set +参数"表示关闭某选项。有时候我们并不需要在启动时用"-x"选项来跟踪所有的命令行,这时我们可以在脚本中使用set命令,这时我们可以在脚本中使用set命令。

set -x    #启动"-x"选项

要跟踪的程序段

set +x     #关闭"-x"选项

set命令同样可以使用上一节中介绍的调试钩子—DEBUG函数来调用,这样可以避免脚本交付使用时删除这些调试语句的麻烦,如以下脚本片段所示:

DEBUG set -x    #启动"-x"选项

要跟踪的程序段

DEBUG set +x    #关闭"-x"选项

当然-x也有美中不足的地方,这个时候我们可以利用bash Shell提供的三个有用的内部变量,可以利用-x选项提示符的限制。

  Shell用于调试的内部变量及其意义

变量名称

意义

LINENO

表示Shell脚本的行号

FUNCNAME

数组变量,表示整个调用链上所有的函数名

PS4

设置-x选项的提示符,默认值是“+”符号

举例:

 

  1. 1 #!/bin/sh
  2. 2 isroot()
  3. 3 {
  4. 4 if [ "$UID" -ne 0 ]
  5. 5 then
  6. 6 return 1
  7. 7 else
  8. 8 return 0
  9. 9 fi
  10. 10 }
  11. 11 echroot()
  12. 12 {
  13. 13 isroot
  14. 14 if [ "$?" -ne 0 ]
  15. 15 then
  16. 16 echo "I'm not root"
  17. 17 else
  18. 18 echo "I'm root"
  19. 19 fi
  20. 20 }而且
  21. 21 #对PS4赋值,定制-x选项的提示符
  22. 22 export PS4='+{$LINENO: ${FUNCNAME[0]}:${FUNCNAME[1]}}'
  23. 23 echroot
  24. [root@localhost shell]# sh -x nestfun
  25. + export 'PS4=+{$LINENO: ${FUNCNAME[0]}:${FUNCNAME[1]}}'
  26. + PS4='+{$LINENO: ${FUNCNAME[0]}:${FUNCNAME[1]}}'
  27. +{23: :}echroot
  28. +{13: echroot:main}isroot
  29. +{4: isroot:echroot}'[' 0 -ne 0 ']'
  30. +{8: isroot:echroot}return 0
  31. +{14: echroot:main}'[' 0 -ne 0 ']'
  32. +{18: echroot:main}echo 'I'\''m root'
  33. I'm root

 

上面的脚本中定义了两个函数isrootechroot,该脚本对PS4变量重新赋值PS4='+{$LINENO: ${FUNCNAME[0]}:${FUNCNAME[1]}}',即-x选项的提示符显示脚本的行号、当前函数名以及调用函数名。FUNCNAME[0]表示当前函数名,FUNCNAME[1]表示调用函数名。

-c选项使Shell解释器从一个字符串而不是一个脚本文件中读取并执行Shell命令,当需要临时测试一小段脚本的执行结果时可使用。

举例:

[root@localhost shell]# sh -c 'a=2;b=3;let c=$a+$b;echo "$c"'

5

注意:单引号是一个字符串,字符串中包含若干条命令,命令之间用分好隔开。

总结:针对Shell脚本中所遇到的错误进行了分析,对于逻辑错误进行的全面分析,trap能够代替echo方便的用于脚本输出信息,对撼树进行跟踪等。tee运用管道,便于清晰的看出管道间的数据流向,调试钩子借鉴于高级程序语言,是很好的编程风格,Shell选项不改变脚本内容,-n-x选项是常见的脚本调试手段,-n主要用于脚本语法错误的调试,-x主要用于脚本的逻辑错误的调试。

分享到:
评论

相关推荐

    shell编程 从入门到精通百度网盘下载地址.

    包括概述、linux文件系统、正则表达式、sed命令和awk编程、文件的排序、合并和分割、变量和引用、操作符、循环与结构化命令、变量的该机用法、I/O...函数、别名、列表和数组、shell脚本调试技术、bash shell编程范例等...

    Linux学习Shell Scripts(脚本)

    六、shell脚本学习总结 一、简单入门(介绍,实例) 1.1 shell script其实就是一个文档(里面包含很多的命令,有简单的格式,一行一行的执行),可以直接用vim编辑(vim很适合写脚本哦)。 现在我们假设你写的这个...

    UNIX操作系统教程 张红光

    Window编程环境介绍295 14.2数据检索加工工具awk296 14.2.1awk基本描述296 14.2.2awk中的...小结303 习题303 附录AUNIX系统中的常用系统调用304 附录BLinux系统中的C环境308 附录CUNIX/Linux常用命令314 参考文献...343

    Hadoop技术内幕:深入解析MapReduce架构设计与实现原理

    阅读源代码前的准备1.1 准备源代码学习环境1.1.1 基础软件下载1.1.2 如何准备...Shell介绍1.5.3 Hadoop Eclipse插件介绍1.6 编译及调试Hadoop源代码1.6.1 编译Hadoop源代码1.6.2 调试Hadoop源代码1.7 小结第2章...

    Android编程入门很简单.(清华出版.王勇).part2

    1.5小结 第2章搭建你的开发环境 2.1配置前的准备工作 2.1.1 Android支持的操作系统 2.1.2准备“四大法宝” 2.2安装并配置JDK 2.2.1 安装JDK 2.2.2配置JDK 2.3安装并配置Eclipse 2.3.1 运行Eclipse 2.3.2 了解Eclipse...

    Android编程入门很简单.(清华出版.王勇).part1

    1.5小结 第2章搭建你的开发环境 2.1配置前的准备工作 2.1.1 Android支持的操作系统 2.1.2准备“四大法宝” 2.2安装并配置JDK 2.2.1 安装JDK 2.2.2配置JDK 2.3安装并配置Eclipse 2.3.1 运行Eclipse 2.3.2 了解Eclipse...

    Android C++高级编程:使用NDK_Onur Cinar, 于红PDF电子书下载 带书签目录 完整版

    Android C++高级编程:使用NDK_Onur Cinar, 于红PDF电子书下载 带书签目录 完整版 原书名:Pro Android C++ with the NDK 原出版社: Apress 作者: (美)Onur Cinar 译者: 于红 佘建伟 冯艳红 ...14.4 小结 344

    SQL.Server.2008编程入门经典(第3版).part1.rar

    1.4 本章小结 第2章 SQL Server管理工具 2.1 联机丛书 2.2 SQLServer配置管理器 2.2.1 服务管理 2.2.2 网络配置 2.2.3 协议 2.2.4 客户端 2.3 SQLServer Management Studio 2.3.1 启动Management Studio 2.3.2 ...

    SQL.Server.2008编程入门经典(第3版).part2.rar

    1.4 本章小结 第2章 SQL Server管理工具 2.1 联机丛书 2.2 SQLServer配置管理器 2.2.1 服务管理 2.2.2 网络配置 2.2.3 协议 2.2.4 客户端 2.3 SQLServer Management Studio 2.3.1 启动Management Studio 2.3.2 ...

    Android开发案例驱动教程 配套代码

    采用案例驱动模式展开讲解知识点,即介绍案例->案例涉及技术->展开知识点->总结的方式 本书作者从事多年一线开发和培训,讲解知识点力求细致,深入浅出 目 录 前言 第1章 Android操作系统概述 1 1.1 Android...

    RED HAT LINUX 6大全

    1.5 小结 4 第2章 Red Hat系统的安装 5 2.1 准备,认真准备 5 2.2 安装Red Hat Linux 6 2.2.1 创建引导盘和辅助盘 6 2.2.2 不利用引导软盘进行安装 7 2.2.3 虚拟控制台 7 2.2.4 对话框 7 2.3 一步步地安装 7 2.3.1 ...

    JAVA程序开发大全---上半部分

    1.4 本章小结 5 第2章 MyEclipse集成开发环境的使用 6 2.1 MyEclipse集成开发工具界面 6 2.1.1 MyEclipse的菜单栏 7 2.1.2 MyEclipse的工具栏 13 2.1.3 MyEclipse的透视图 14 2.1.4 MyEclipse的视图 17 2.1.5 ...

    linux programming instances网络编程教程 附源代码

    1.5 本章小结 第2章 进程间通信 2.1 管道和fif0 2.1.1 管道的创建和使用 .2.1.2 实例 2.1.3 popen和pclose函数 2.1.4 fifo的创建和使用 2.1.5 用fif0实现多客户服务 2.1.6 系统对管道和fif0的限制 ...

    零基础Python爬虫48小时速成课.txt

    02 1.02爬虫技术库及反爬说明.mp4 03 1.03百度搜索及文件下载.mp4 04 1.04百度翻译之urllib的POST请求.mp4 05 1.05复杂的GET请求多页数据.mp4 06 1.06urllib的build_opener及handlers.mp4 07 1.07上下文扩展和...

    Linux管理员指南

    比较 3 1.3.2 GUI图形界面与操作系统内核的彼 此相对独立 4 1.3.3 Windows中的“网络邻居”概念 5 1.3.4 Windows中的注册表文件与文本文 件的比较 6 1.3.5 域的概念 6 1.4 小结 7 第2章 ...

    集群好书《高性能Linux服务器构建实战》 试读章节下载

    由国内著名技术社区联合推荐的2012年IT技术力作:《高性能Linux服务器构建实战:运维监控、性能调优与集群应用》,即将上架发行,此书从Web应用、数据备份与恢复、网络存储应用、运维监控与性能优化、...14.6 本章小结

    Tcl_TK编程权威指南pdf

    这种早期的设想就是让应用程序由包含编译代码的大块实体和一小部分用于进行配置和编写高级命令的Tcl代码组成。John的编辑器皿,还有终端仿真程序tx就遵循了这种模式。虽然这种模式仍然是有效的,但结果表明用Tcl来...

    自己动手写操作系统(含源代码).part2

    本书不仅介绍操作系统的各要素,同时涉及开发操作系统需要的各个方面,比如如何建立开发环境、如何调试以及如何在虚拟机中运行等。书中的实例操作系统采用IA32作为默认平台,所以保护模式也作为必备知识储备收入书...

    自己动手写操作系统(含源代码).part1

    本书不仅介绍操作系统的各要素,同时涉及开发操作系统需要的各个方面,比如如何建立开发环境、如何调试以及如何在虚拟机中运行等。书中的实例操作系统采用IA32作为默认平台,所以保护模式也作为必备知识储备收入书...

Global site tag (gtag.js) - Google Analytics