`
无心出岫
  • 浏览: 1406 次
  • 性别: 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图形化总结

    本文将详细总结Shell脚本中的一些关键知识点,主要涉及命令组、重定向、进程替换、信号捕获、条件测试、函数定义和脚本调试等。 命令组与子shell: 在Shell中,可以使用大括号`{}`来组合多个命令,使其在同一个...

    shell 编程小结

    ### Shell编程小结 在IT领域,特别是操作系统与脚本编程方面,Shell占据了举足轻重的地位。Shell作为用户与操作系统之间沟通的桥梁,在UNIX和Linux系统中扮演着核心角色。本文将深入探讨Shell编程的基础知识、核心...

    Visual Studio 2013 Shell 下载

    作为 Visual Studio SDK 的一部分,Visual Studio Shell 与 Visual Studio 平台紧密集成,确保了开发者可以利用 Visual Studio 生态系统中的所有资源和技术文档进行开发工作。 #### 三、应用场景 ##### 1. **教育...

    Windows shell编程.pdf

    Windows Shell编程是一种强大的技术,能够让开发者创造出与Windows操作系统无缝集成的应用程序。通过掌握Shell API、注册表操作、Shell扩展以及脚本环境等相关知识,开发者不仅能够提升自己的技能水平,还能为用户...

    Visual C++ Windows Shell Programming

    ### Visual C++ Windows Shell 编程相关知识点 #### 一、引言 随着技术的不断发展,用户界面(UI)的设计与实现变得越来越重要。在Windows操作系统中,**Shell**不仅为用户提供了一个友好的图形界面,同时也提供了...

    基于单片机的简易shell

    总结来说,基于单片机的简易shell是一个实用的工具,它结合了C语言编程、单片机硬件控制、命令解析和参数处理等多个技术点。通过理解这些知识点,开发者可以更好地理解和应用这个压缩包中的代码,实现自己的单片机...

    makefile调试技术

    ### makefile调试技术详解 #### 一、makefile调试概览 在开发过程中,makefile是一种常见的用于自动化构建过程的工具。随着项目复杂度的提高,编写和维护makefile也变得越来越具有挑战性。因此,掌握makefile的...

    shell编程之从零到壹--大神成就之路

    手工代码发布与脚本发布代码章节讲解了从方案分析到实施,再到调试、总结的全过程。简单脚本编写关注命令罗列、变量化、函数化和远程执行等方面,而大型脚本编写则包括功能框架搭建、命令填充、日志功能、锁文件功能...

    shell必学必会命令

    ### Shell 必学必会命令 ...- **生产脚本编写总结**:总结编写简单和复杂脚本的经验教训。 以上内容是Shell学习的基础与进阶知识汇总,通过掌握这些内容,可以更好地进行运维自动化、脚本编写等工作。

    jxta-shell-2.4.zip

    JXTA Shell 2.4作为一个强大的工具,不仅简化了JXTA网络的管理和调试,还为开发者提供了丰富的功能来实现P2P应用。通过深入理解和熟练使用JXTA Shell,可以极大地提高开发和维护JXTA应用的效率。无论是新手还是经验...

    shell编程实例

    ### Shell编程实例详解 #### Bourne Shell 及其编程基础 **Bourne Shell**,简称 **sh**,是Unix操作系统中最古老的Shell之一,由贝尔实验室的Stephen Bourne...希望本文能帮助读者更好地理解和运用 Shell 编程技术。

    Microsoft.VisualStudio.Shell.dll下载

    总结来说,Microsoft.VisualStudio.Shell.dll是Visual Studio的核心组件,它对于构建高效、个性化的开发环境以及实现各种扩展功能起着决定性的作用。理解和掌握其工作原理,对于提升开发效率和解决相关问题具有重要...

Global site tag (gtag.js) - Google Analytics