- 论坛徽章:
- 0
|
Bash shell 没有自带调试器, 甚至没有任何调试类型的命令或结构.
[1]
脚本里的语法错误或拼写错误会产生含糊的错误信息,通常这些在调试非功能性的脚本时没什么帮助.
例子 29-1. 一个错误的脚本
1 #!/bin/bash
2 # ex74.sh
3
4 # 这是一个错误的脚本.
5 # 哪里有错?
6
7 a=37
8
9 if [$a -gt 27 ]
10 then
11 echo $a
12 fi
13
14 exit 0
脚本的输出:
./ex74.sh: [37: command not found上面的脚本有什么错误(线索: 注意if的后面)?
例子 29-2. 丢失
关键字(keyword)
1 #!/bin/bash
2 # missing-keyword.sh: 会产生什么样的错误信息?
3
4 for a in 1 2 3
5 do
6 echo "$a"
7 # done # 第7行的必需的关键字 'done' 被注释掉了.
8
9 exit 0
脚本的输出:
missing-keyword.sh: line 10: syntax error: unexpected end of file
注意错误信息中说明的错误行不必一定要参考, 但那行是Bash解释器最终认识到是个错误的地方.
出错信息可能在报告语法错误的行号时会忽略脚本的注释行.
如果脚本可以执行,但不是你所期望的那样工作怎么办? 这大多是由于常见的逻辑错误产生的.
例子 29-3. test24, 另一个错误脚本
1 #!/bin/bash
2
3 # 这个脚本目的是为了删除当前目录下的所有文件,包括文件名含有空格的文件。
4 #
5 # 但不能工作.
6 # 为什么?
7
8
9 badname=`ls | grep ' '`
10
11 # 试试这个:
12 # echo "$badname"
13
14 rm "$badname"
15
16 exit 0
为了找出
例子 29-3
的错误可以把echo "$badname" 行的注释去掉. echo 出来的信息对你判断是否脚本以你希望的方式运行时很有帮助.
在这个实际的例子里, rm "$badname" 不会达到想要的结果,因为$badname 没有引用起来. 加上引号以保证rm 命令只有一个参数(这就只能匹配一个文件名). 一个不完善的解决办法是删除A partial fix is to remove to quotes from $badname and to reset $IFS to contain only a newline, IFS=$'\n'. 不过, 存在更简单的办法.
1 # 修正删除包含空格文件名时出错的办法.
2 rm *\ *
3 rm *" "*
4 rm *' '*
5 # Thank you. S.C.
总结该脚本的症状,
终止于一个"syntax error"(语法错误)的信息, 或
它能运行, 但不是按期望的那样运行(逻辑错误).
它能运行,运行的和期望的一样, 但有讨厌的副作用 (逻辑炸弹).
用来调试不能工作的脚本的工具包括
echo
语句可用在脚本中的有疑问的点上以跟踪了解变量的值, 并且也可以了解后续脚本的动作.
![]()
最好只在调试时才使用echo语句.
1 ### debecho (debug-echo), by Stefano Falsetto ###
2 ### 只有变量 DEBUG 设置了值时才会打印传递进来的变量值. ###
3 debecho () {
4 if [ ! -z "$DEBUG" ]; then
5 echo "$1" >&2
6 # ^^^ 打印到标准出错
7 fi
8 }
9
10 DEBUG=on
11 Whatever=whatnot
12 debecho $Whatever # whatnot
13
14 DEBUG=
15 Whatever=notwhat
16 debecho $Whatever # (这儿就不会打印了.)
使用
tee
过滤器来检查临界点的进程或数据流.
设置选项 -n -v -x
sh -n scriptname 不会实际运行脚本,而只是检查脚本的语法错误. 这等同于把 set -n 或 set -o noexec 插入脚本中. 注意还是有一些语法错误不能被这种检查找出来.
sh -v scriptname 在实际执行一个命令前打印出这个命令. 这也等同于在脚本里设置 set -v 或 set -o verbose.
选项 -n 和 -v 可以一块使用. sh -nv scriptname 会打印详细的语法检查.
sh -x scriptname 打印每个命令的执行结果, 但只用在某些小的方面. 它等同于脚本中插入 set -x 或 set -o xtrace.
把 set -u 或 set -o nounset 插入到脚本里并运行它, 就会在每个试图使用没有申明过的变量的地方打印出一个错误信息.
使用一个"assert"(断言) 函数在脚本的临界点上测试变量或条件. (这是从C语言中借用来的.)
例子 29-4 用"assert"测试条件
1 #!/bin/bash
2 # assert.sh
3
4 assert () # 如果条件测试失败,
5 { #+ 则打印错误信息并退出脚本.
6 E_PARAM_ERR=98
7 E_ASSERT_FAILED=99
8
9
10 if [ -z "$2" ] # 没有传递足够的参数.
11 then
12 return $E_PARAM_ERR # 什么也不做就返回.
13 fi
14
15 lineno=$2
16
17 if [ ! $1 ]
18 then
19 echo "Assertion failed: \"$1\""
20 echo "File \"$0\", line $lineno"
21 exit $E_ASSERT_FAILED
22 # else
23 # return
24 # 返回并继续执行脚本后面的代码.
25 fi
26 }
27
28
29 a=5
30 b=4
31 condition="$a -lt $b" # 会错误信息并从脚本退出.
32 # 把这个“条件”放在某个地方,
33 #+ 然后看看有什么现象.
34
35 assert "$condition" $LINENO
36 # 脚本以下的代码只有当"assert"成功时才会继续执行.
37
38
39 # 其他的命令.
40 # ...
41 echo "This statement echoes only if the \"assert\" does not fail."
42 # ...
43 # 余下的其他命令.
44
45 exit 0
用变量
$LINENO
和内建的
caller
.
捕捉exit.
脚本中的The exit 命令会触发信号0,终结进程,即脚本本身.
[2]
这常用来捕捉exit命令做某事, 如强制打印变量值. trap 命令必须是脚本中第一个命令.
捕捉信号
trap
当收到一个信号时指定一个处理动作; 这在调试时也很有用.
![]()
信号是发往一个进程的非常简单的信息, 要么是由内核发出要么是由另一个进程, 以告诉接收进程采取一些指定的动作 (一般是中止). 例如, 按Control-C, 发送一个用户中断( 即 INT 信号)到运行中的进程.
1 trap '' 2
2 # 忽略信号 2 (Control-C), 没有指定处理动作.
3
4 trap 'echo "Control-C disabled."' 2
5 # 当按 Control-C 时显示一行信息.
例子 29-5. 捕捉 exit
1 #!/bin/bash
2 # 用trap捕捉变量值.
3
4 trap 'echo Variable Listing --- a = $a b = $b' EXIT
5 # EXIT 是脚本中exit命令产生的信号的信号名.
6 #
7 # 由"trap"指定的命令不会被马上执行,只有当发送了一个适应的信号时才会执行。
8 #
9
10 echo "This prints before the \"trap\" --"
11 echo "even though the script. sees the \"trap\" first."
12 echo
13
14 a=39
15
16 b=36
17
18 exit 0
19 # 注意到注释掉上面一行的'exit'命令也没有什么不同,
20 #+ 这是因为执行完所有的命令脚本都会退出.
例子 29-6. 在Control-C后清除垃圾
1 #!/bin/bash
2 # logon.sh: 简陋的检查你是否还处于连线的脚本.
3
4 umask 177 # 确定临时文件不是全部用户都可读的.
5
6
7 TRUE=1
8 LOGFILE=/var/log/messages
9 # 注意 $LOGFILE 必须是可读的
10 #+ (用 root来做:chmod 644 /var/log/messages).
11 TEMPFILE=temp.$$
12 # 创建一个"唯一的"临时文件名, 使用脚本的进程ID.
13 # 用 'mktemp' 是另一个可行的办法.
14 # 举例:
15 # TEMPFILE=`mktemp temp.XXXXXX`
16 KEYWORD=address
17 # 上网时, 把"remote IP address xxx.xxx.xxx.xxx"这行
18 # 加到 /var/log/messages.
19 ONLINE=22
20 USER_INTERRUPT=13
21 CHECK_LINES=100
22 # 日志文件中有多少行要检查.
23
24 trap 'rm -f $TEMPFILE; exit $USER_INTERRUPT' TERM INT
25 # 如果脚本被control-c中断了,则清除临时文件.
26
27 echo
28
29 while [ $TRUE ] #死循环.
30 do
31 tail -$CHECK_LINES $LOGFILE> $TEMPFILE
32 # 保存系统日志文件的最后100行到临时文件.
33 # 这是需要的, 因为新版本的内核在登录网络时产生许多日志文件信息.
34 search=`grep $KEYWORD $TEMPFILE`
35 # 检查"IP address" 短语是不是存在,
36 #+ 它指示了一次成功的网络登录.
37
38 if [ ! -z "$search" ] # 引号是必须的,因为变量可能会有一些空白符.
39 then
40 echo "On-line"
41 rm -f $TEMPFILE # 清除临时文件.
42 exit $ONLINE
43 else
44 echo -n "." # -n 选项使echo不会产生新行符,
45 #+ 这样你可以从该行的继续打印.
46 fi
47
48 sleep 1
49 done
50
51
52 # 注: 如果你更改KEYWORD变量的值为"Exit",
53 #+ 这个脚本就能用来在网络登录后检查掉线
54 #
55
56 # 练习: 修改脚本,像上面所说的那样,并修正得更好
57 #
58
59 exit 0
60
61
62 # Nick Drage 建议用另一种方法:
63
64 while true
65 do ifconfig ppp0 | grep UP 1> /dev/null && echo "connected" && exit 0
66 echo -n "." # 在连接上之前打印点 (.....).
67 sleep 2
68 done
69
70 # 问题: 用 Control-C来终止这个进程可能是不够的.
71 #+ (点可能会继续被打印.)
72 # 练习: 修复这个问题.
73
74
75
76 # Stephane Chazelas 也提出了另一个办法:
77
78 CHECK_INTERVAL=1
79
80 while ! tail -1 "$LOGFILE" | grep -q "$KEYWORD"
81 do echo -n .
82 sleep $CHECK_INTERVAL
83 done
84 echo "On-line"
85
86 # 练习: 讨论这几个方法的优缺点.
87 #
![]()
trap 的DEBUG参数在每个命令执行完后都会引起一个指定的执行动作,例如,这可用来跟踪变量。.
例子 29-7. 跟踪变量
1 #!/bin/bash
2
3 trap 'echo "VARIABLE-TRACE> \$variable = \"$variable\""' DEBUG
4 # 在每个命令行显示变量$variable 的值.
5
6 variable=29
7
8 echo "Just initialized \"\$variable\" to $variable."
9
10 let "variable *= 3"
11 echo "Just multiplied \"\$variable\" by 3."
12
13 exit $?
14
15 # "trap 'command1 . . . command2 . . .' DEBUG" 的结构适合复杂脚本的环境
16 #+ 在这种情况下多次"echo $variable"比较没有技巧并且也耗时.
17 #
18 #
19
20 # Thanks, Stephane Chazelas 指出这一点.
21
22
23 脚本的输出:
24
25 VARIABLE-TRACE> $variable = ""
26 VARIABLE-TRACE> $variable = "29"
27 Just initialized "$variable" to 29.
28 VARIABLE-TRACE> $variable = "29"
29 VARIABLE-TRACE> $variable = "87"
30 Just multiplied "$variable" by 3.
31 VARIABLE-TRACE> $variable = "87"
当然, trap 命令除了调试还有其他的用处.
例子 29-8. 运行多进程 (在多处理器的机器里)
1 #!/bin/bash
2 # parent.sh
3 # 在多处理器的机器里运行多进程.
4 # 作者: Tedman Eng
5
6 # 这是要介绍的两个脚本的第一个,
7 #+ 这两个脚本都在要在相同的工作目录下.
8
9
10
11
12 LIMIT=$1 # 要启动的进程总数
13 NUMPROC=4 # 当前进程数 (forks?)
14 PROCID=1 # 启动的进程ID
15 echo "My PID is $$"
16
17 function start_thread() {
18 if [ $PROCID -le $LIMIT ] ; then
19 ./child.sh $PROCID&
20 let "PROCID++"
21 else
22 echo "Limit reached."
23 wait
24 exit
25 fi
26 }
27
28 while [ "$NUMPROC" -gt 0 ]; do
29 start_thread;
30 let "NUMPROC--"
31 done
32
33
34 while true
35 do
36
37 trap "start_thread" SIGRTMIN
38
39 done
40
41 exit 0
42
43
44
45 # ======== 下面是第二个脚本 ========
46
47
48 #!/bin/bash
49 # child.sh
50 # 在多处理器的机器里运行多进程.
51 # 这个脚本由parent.sh脚本调用(即上面的脚本).
52 # 作者: Tedman Eng
53
54 temp=$RANDOM
55 index=$1
56 shift
57 let "temp %= 5"
58 let "temp += 4"
59 echo "Starting $index Time:$temp" "$@"
60 sleep ${temp}
61 echo "Ending $index"
62 kill -s SIGRTMIN $PPID
63
64 exit 0
65
66
67 # ======================= 脚本作者注 ======================= #
68 # 这不是完全没有bug的脚本.
69 # 我运行LIMIT = 500 ,在过了开头的一二百个循环后,
70 #+ 这些进程有一个消失了!
71 # 不能确定是不是因为捕捉信号产生碰撞还是其他的原因.
72 # 一但信号捕捉到,在下一个信号设置之前,
73 #+ 会有一个短暂的时间来执行信号处理程序,
74 #+ 这段时间内很可能会丢失一个信号捕捉,因此失去生成一个子进程的机会.
75
76 # 毫无疑问会有人能找出这个bug的原因,并且修复它
77 #+ . . . 在将来的某个时候.
78
79
80
81 # ===================================================================== #
82
83
84
85 # ----------------------------------------------------------------------#
86
87
88
89 #################################################################
90 # 下面的脚本由Vernia Damiano原创.
91 # 不幸地是, 它不能正确工作.
92 #################################################################
93
94 #!/bin/bash
95
96 # 必须以最少一个整数参数来调用这个脚本
97 #+ (这个整数是协作进程的数目).
98 # 所有的其他参数被传给要启动的进程.
99
100
101 INDICE=8 # 要启动的进程数目
102 TEMPO=5 # 每个进程最大的睡眼时间
103 E_BADARGS=65 # 没有参数传给脚本的错误值.
104
105 if [ $# -eq 0 ] # 检查是否至少传了一个参数给脚本.
106 then
107 echo "Usage: `basename $0` number_of_processes [passed params]"
108 exit $E_BADARGS
109 fi
110
111 NUMPROC=$1 # 协作进程的数目
112 shift
113 PARAMETRI=( "$@" ) # 每个进程的参数
114
115 function avvia() {
116 local temp
117 local index
118 temp=$RANDOM
119 index=$1
120 shift
121 let "temp %= $TEMPO"
122 let "temp += 1"
123 echo "Starting $index Time:$temp" "$@"
124 sleep ${temp}
125 echo "Ending $index"
126 kill -s SIGRTMIN $$
127 }
128
129 function parti() {
130 if [ $INDICE -gt 0 ] ; then
131 avvia $INDICE "${PARAMETRI[@]}" &
132 let "INDICE--"
133 else
134 trap : SIGRTMIN
135 fi
136 }
137
138 trap parti SIGRTMIN
139
140 while [ "$NUMPROC" -gt 0 ]; do
141 parti;
142 let "NUMPROC--"
143 done
144
145 wait
146 trap - SIGRTMIN
147
148 exit $?
149
150 :
![]()
trap '' SIGNAL (两个引号引空) 在脚本中禁用了 SIGNAL 信号的动作(即忽略了). trap SIGNAL 则恢复了 SIGNAL 信号前次的处理动作. 这在保护脚本的某些临界点的位置不受意外的中断影响时很有用.
1 trap '' 2 # 信号 2是 Control-C, 现在被忽略了.
2 command
3 command
4 command
5 trap 2 # 再启用Control-C
6
Bash的
版本
3
增加了下面的特殊变量用于调试.
$BASH_ARGC
$BASH_ARGV
$BASH_COMMAND
$BASH_EXECUTION_STRING
$BASH_LINENO
$BASH_SOURCE
$BASH_SUBSHELL
注
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/104504/showart_2063908.html |
|