trap命令
trap命令用于捕获指定的信号并执行预定义的命令:
trap 'command' signal
其中signal是要捕获的信号,command是捕获到指定的信号之后,所要执行的命令,可以是一条或多条合法的shell语句,也可以是一个函数名。
shell脚本在执行时,有三个“伪信号”(之所以称之为“伪信号”是因为由shell产生):
伪信号 | 何时产生 |
---|---|
EXIT | 脚本执行完毕 |
ERR | 当一条命令返回非零时(代表命令执行不成功) |
DEBUG | 脚本中每一条命令执行之前 |
在调试过程中,为了跟踪某些变量的值,我们常常需要在shell脚本的许多地方插入相同的echo语句来打印相关变量的值,这种做法显得烦琐而笨拙。而通过捕获DEBUG信号,我们只需要一条trap语句就可以完成对相关变量的全程跟踪。
以下是一个通过捕获DEBUG信号来跟踪变量的示例程序:
$ cat exp1.sh
1 trap 'echo "before execute line:$LINENO, a=$a,b=$b,c=$c"' DEBUG
2 a=1
3 if [ "$a" -eq 1 ]
4 then
5 b=2
6 else
7 b=1
8 fi
9 c=3
10 echo "end"
$ bash exp1.sh
before execute line:2, a=,b=,c=
before execute line:3, a=1,b=,c=
before execute line:5, a=1,b=,c=
before execute line:9, a=1,b=2,c=
before execute line:10, a=1,b=2,c=3
end
调试钩子
通过定义一个DEBUG函数可以使植入调试钩子的过程简洁方便,如下面代码所示:
$ cat –n exp2.sh
1 DEBUG()
2 {
3 if [ "$DEBUG" = "true" ]; then
4 $@
5 fi
6 }
7 DEBUG="true"
8 a=1
9 DEBUG echo "a=$a"
10 if [ "$a" -eq 1 ]; then
11 b=2
12 else
13 b=1
14 fi
15 DEBUG echo "b=$b"
16 c=3
17 DEBUG echo "c=$c"
$ bash exp2.sh
a=1
b=2
c=3
shell的执行选项
本节将介绍几个shell常用选项的用法:
-c "string" 从string中读取命令
-n, -o noexec 只读取shell脚本,但不实际执行,用于测试shell脚本是否存在语法错误
-x, -o xtrace 进入跟踪方式,显示所执行的每一条命令
-u, -o nounset 发现变量未赋值便使用,报错并停止执行
-f, -o noglob 不对通配符进行文件名扩展
-v, -o verbose 打印 Shell 接收到的每一行输入
-e, -o errexit 发现命令出错(返回非0),报错并停止执行
-o pipefail 发现通过管道连接的多条命令中任一出错,报错并停止执行(通过管道连接的
多条命令的返回值是最后一个命令的返回值,所以没法用-e追踪)
shell的执行选项除了可以在启动shell时指定外,亦可在脚本中用set命令来指定。 "set -参数"表示启用某选项,"set +参数"表示关闭某选项,比如set -x/set -o xtrace和set +x/set +o xtrace。
“-x”选项使shell在执行脚本的过程中把它实际执行的每一个命令显示出来,并且在行首显示一个"+"号, "+"号后面显示的是经过了变量替换之后的命令行的内容,有助于分析实际执行的是什么命令。
如果把本文前面所述的trap ‘command’ DEBUG机制与“-x”选项结合起来,我们就可以既输出实际执行的每一条命令,又逐行跟踪相关变量的值,对调试相当有帮助。
仍以前面所述的exp2.sh为例,现在加上“-x”选项来执行它:
$ sh –x exp2.sh
+ trap 'echo "before execute line:$LINENO, a=$a,b=$b,c=$c"' DEBUG
++ echo 'before execute line:2, a=,b=,c='
before execute line:2, a=,b=,c=
+ a=1
++ echo 'before execute line:3, a=1,b=,c='
before execute line:3, a=1,b=,c=
+ '[' 1 -eq 1 ']'
++ echo 'before execute line:5, a=1,b=,c='
before execute line:5, a=1,b=,c=
+ b=2
++ echo 'before execute line:9, a=1,b=2,c='
before execute line:9, a=1,b=2,c=
+ c=3
++ echo 'before execute line:10, a=1,b=2,c=3'
before execute line:10, a=1,b=2,c=3
+ echo end
end
在上面的结果中,前面有“+”号的行是shell脚本实际执行的命令,前面有“++”号的行是执行trap机制中指定的命令,其它的行则是输出信息。
有时候我们并不需要在启动时用"-x"选项来跟踪所有的命令行,这时我们可以在脚本中使用set命令。set命令同样可以使用上一节中介绍的调试钩子—DEBUG函数来调用,这样可以避免脚本交付使用时删除这些调试语句的麻烦,如以下脚本片段所示:
DEBUG set -x
要跟踪的程序段
DEBUG set +x
环境变量
PS1是主提示符变量,默认值 [\u@\h \W]\$
,显示用户主机名称和工作目录。
一个长命令可以通过在末尾加 \
使其分行显示,PS2是多行命令的默认提示符,默认值 >
。
PS3是Shell脚本中使用select时的提示符:
$ cat ps3.sh
PS3="Select a day (1-4): "
select i in mon tue wed exit
do
case $i in
mon) echo "Monday";;
tue) echo "Tuesday";;
wed) echo "Wednesday";;
exit) exit;;
esac
done
$ bash ps3.sh
1) mon
2) tue
3) wed
4) exit
Select a day (1-4): 1
Monday
Select a day (1-4): 2
Tuesday
Select a day (1-4): 4
PS4是被显示在“-x”选项输出的每一条命令前面的提示符,默认值 +
。
LINENO
在脚本里面的行号
FUNCNAME
函数名字,是一个数组变量,包含了整个调用链上所有函数的名字,
${FUNCNAME[0]}代表shell脚本当前正在执行的函数的名字,
${FUNCNAME[1]}则代表调用函数${FUNCNAME[0]}的函数的名字,依此类推。
BASH_SOURCE
脚本名字,是一个数组变量,包含了整个调用链上所有脚本的名字,
${BASH_SOURCE[0]}是当前执行的脚本,${BASH_SOURCE[1]}是调用当前脚本的脚本,依此类推。
BASH_LINENO
每一轮调用对应的行号,是一个数组变量,${BASH_LINENO[$i]}表示${FUNCNAME[$i]}在调用它的
脚本文件${BASH_SOURCE[$i+1]}里面的行号。
修改PS4的值,再执行一次exp2.sh:
$ export PS4='+{$LINENO:${FUNCNAME[0]}} '
$ bash -x exp2.sh
+{1:main} trap 'echo "before execute line:$LINENO, a=$a,b=$b,c=$c"' DEBUG
++{2:main} echo 'before execute line:2, a=,b=,c='
before execute line:2, a=,b=,c=
+{2:main} a=1
++{3:main} echo 'before execute line:3, a=1,b=,c='
before execute line:3, a=1,b=,c=
+{3:main} '[' 1 -eq 1 ']'
++{5:main} echo 'before execute line:5, a=1,b=,c='
before execute line:5, a=1,b=,c=
+{5:main} b=2
++{9:main} echo 'before execute line:9, a=1,b=2,c='
before execute line:9, a=1,b=2,c=
+{9:main} c=3
++{10:main} echo 'before execute line:10, a=1,b=2,c=3'
before execute line:10, a=1,b=2,c=3
+{10:main} echo end
end