Shell 脚本调试方法

我们在使用 Unix-like 系统时, shell编程是必不可少的, 在编写shell脚本的时候也不可避免会产生bug, 所以我们就需要学习shell脚本的调试方法.

1. 直接调试

何为直接调试, 相信大家在编写c/c++程序调试时候都经常会在程序中加一个 printf 用来输出中间值达到调试的效果. 当然, 在shell脚本调试的时候这种方式也是同样适用的, 我们在程序中添加 echo 达到输出中间值的效果, 这里就不做过多的赘述.
如下面这个脚本

#!/bin/sh
mkdir file_dir
cd file_dir
for ((i=0;i<10;i++))
do
    file_name=file_$i.txt
    touch $file_name
    echo $file_name
done

2. 使用 Shell 脚本调试命令调试

shell 提供了几种调试参数

参数 说明 应用
-n 只读取shell脚本,但不实际执行 测试shell脚本是否存在语法错误
-x 进入跟踪方式,显示所执行的每一条命令 使shell在执行脚本的过程中把它实际执行的每一个命令行显示出来
-c 从strings中读取命令 临时测试一小段脚本的执行结果

此命令是执行下叙的调试命令(tee)的调试代码加上 -x 参数的结果, 执行每一条命令并打印出来

[YancyKahn@DESKTOP-VILIJH9~/Program/shell-script]$sh -x test06.sh
+ ifconfig eth0
+ grep inet addr
+ tee iptmp.txt
+ cut -d : -f 3
+ ipaddr=255.255.0.0
+ echo 255.255.0.0
255.255.0.0

3. 使用 Linux 系统命令调试

在使用linux系统命令进行调试时, 我们需要更改shell脚本的源代码, 进行调试. 主要思想是在其中加入我们需要输出的信息, 这里主要使用trap和tee命令

1. 调试命令

1.trap

参考至文献[1.]
参考至文献[2.]
trap命令用于指定在接收到信号后将要采取的动作,常见的用途是在脚本程序被中断时完成清理工作。当shell接收到sigspec指定的信号时,arg参数(命令)将会被读取,并被执行。

trap [-lp] [[arg] sigspec ...]

在调试的时候我们使用trap命令时, 可以将命令简化为:

trap 'command' signal   //command 的引号是必须的

其中, signal 是要捕获的信号, command是捕获到signal后要执行的命令. 我么将trap命令写到 shell 脚本中. 根据捕获到的信号得到输出或者执行某些命令. shell 脚本在执行的过程中会产生伪信号. 何为伪信号, 伪信号是有非系统产生的信号, 即为伪信号, 在这里为 shell 产生的信号

shell中的伪信号:

信号名 何时产生 使用说明
EXIT 从一个函数中退出或者整个脚本执行结束 在函数退出或者脚本终止时可以输出想要跟踪的变量值
ERR 当一个命令返回非零状态时(命令执行失败) 追踪运行失败的命令并输出调试信息
DEBUG 脚本中的每一条命令执行前 可以实现相关变量的全程跟踪

运行实例

捕获ERR信息

#/bin/sh
NOT_FOUND_ERROR()
{
    echo "[LINE: $1] Error command not found exited with status $?"
}
foo1()
{
    return 1;
}
foo2()
{
    return 0;
}
trap 'NOT_FOUND_ERROR $LINENO' ERR
foo
foo1
foo2

捕获DEBUG信息

#!/bin/sh
echo "DEBUG BEGIN"
trap 'echo [Debug: $LINENO] value d=$d, e=$e, f=$g' DEBUG
a=1
b=2
c=3
for ((i=0;i<5;i++))
do
    d=`expr $d + $a`
    e=`expr $e + $b`
    f=`expr $f + $c`
done
echo "END"

> ### 2. tee >在shell脚本中管道以及输出输出重定向使用很多, 在起作用下一些命令的执行结果可能就成了下一条命令的输入, 因为使用管道和输入输出重定向, 所以中间的输出我们是不可见的. 因为输出不可见所以调试就异常困难, tee命令可以帮助我们很好的去调试这种shell脚本 首先我们先复习一下管道和tee命令: *** 1. 管道是Linux中很重要的一种通信方式,是把一个程序的输出直接连接到另一个程序的输入. *** *** 2. tee命令用于将数据重定向到文件,另一方面还可以提供一份重定向数据的副本作为后续命令的stdin。简单的说就是把数据重定向到给定文件和屏幕上. ***
ls | tee file_name.txt   //把ls命令执行的结果输出在屏幕和file_name.txt文件中

tee 调试实例
现在有如下脚本想要获得掩码地址

ipaddr=`ifconfig eth0 | grep "inet addr" | cut ' ' : -f 3`
echo $ipaddr

输出为

cut:  : No such file or directory
cut: :: No such file or directory

我们并不知道具体哪里出了错, 在程序中加入tee把数据引流出来, 我们看中间的数据有什么问题

ipaddr=`ifconfig eth0 | grep "inet addr" | tee iptemp.txt| cut ' ' : -f 3`
echo $ipaddr

查看中间输出iptemp.txt的数据

          inet addr:169.254.14.12  Mask:255.255.0.0

我们发现他们中间是以制表符分割的, 所以讲程序改为

ipaddr=`ifconfig eth0 | grep "inet addr" | cut -d : -f 3`
echo $ipaddr

正常输出得到掩码地址

255.255.0.0

## 4. DEBUG宏调试 在c语言中我们经常使用DEBUG宏来进行调试, 在shell脚本同样可以使用相同的思想进行调试
if [ “$DEBUG” = “true” ]; then
echo “DEBUGGING INFO”  #此处可以输出调试信息
fi

附录

1. Linux中的奇怪变量的含义

参考至文献[3.]

// $# 是传给脚本的参数个数

// $0 是脚本本身的名字

// $1 是传递给该shell脚本的第一个参数

// $2 是传递给该shell脚本的第二个参数

// $@ 是传给脚本的所有参数的列表

// $* 是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个

// $$ 是脚本运行的当前进程ID号

// $? 是显示最后命令的退出状态,0表示没有错误,其他表示有错误

2. shell 脚本常量

参考文献

[1.] https://www.ibm.com/developerworks/cn/linux/l-cn-shell-debug/index.html
[2.] http://man.linuxde.net/trap
[3.] http://blog.51cto.com/dadekey/119938
[4.] javascript:void(0)