第一章:The Missing Code Library--13.调试Shell脚本
虽然这节并不包含一个真实的脚本,但是在此谈谈开发和调试shell脚本的基础是很有益处的。因为,bug是一定会出现的。我发现的最好的调试策略就是渐增性的生成脚本。一部分脚本程序员对于首次运行即会OK保持着高度乐观的态度,但我发现由小处开始,在一个适宜的规模上的,会很有用处。此外,自由使用echo语句来追踪变量,以及-x选项来显示调试输出,都是很有用的。为了看看它们是怎么做的,我们来调试一个简单的猜数字游戏。
代码:
1 #!/bin/sh 2 # hilow -- A simple number-guessing game 3 4 biggest=100 # maximum number possible 5 guess=0 # guessed by player 6 guesses=0 # number of guesses made 7 number=$(($$ % $biggest) # random number, between 1 and $biggest 8 9 while [ $guess -ne $number ] ; do 10 echo -n "Guess? " ; read answer 11 if [ "$guess" -lt $number ] ; then 12 echo "... bigger!" 13 elif [ "$guess" -gt $number ] ; then 14 echo "... smaller! 15 fi 16 guesses=$(($guesses + 1) 17 done 18 19 echo "Right!! Guessed $number in $guesses guesses." 20 21 exit 0
运行脚本:
调试这个脚本的第一步就是测试下确定生成的数字是完全随机的。我们要获得正在运行的这个脚本所在shell的进程ID,使用$$,然后用%将它减少到一个可以使用的范围内。为了测试这个功能,先直接在命令行上键入:
1 echo $(($$%100)) 2 43 3 echo $(($$%100)) 4 43 5 echo $(($$%100)) 6 43
可以运行,但不怎么随机。为什么?当命令直接在命令行上运行时,PID(进程ID)总是相同的。当在一个脚本中时,命令每次运行都是在一个不同的子shell中,所以PID会变化。下一步是给这个游戏增加基本的逻辑。生成一个1-100的随机数,玩家猜数字,告诉玩家猜的数字是高了还是低了,直到玩家猜出数字。在键入所有的基本的代码后,是时候运行下脚本了。
1 ./hilow.sh 2 ./hilow.sh: line 19: unexpected EOF while looking for matching `"' 3 ./hilow.sh: line 23: syntax error: unexpected end of file
哈,脚本开发人员的烦恼来了:一个意外的EOF。要搞明白这条信息,回忆下引用起来的信息可能包括换行,所以错误表明在19行并不意味着它就在那儿。它只是说明shell读到那儿,一直错误的匹配引号,除非它能遇到最后一个引号才会停止,此时它意识到一定是哪儿出错了。事实上,19行完全正确:
1 sed -n 19p hilow.sh 2 echo "Right!! Guessed $number in $guesses guesses."
因此,问题肯定出现在脚本中之前的位置。从shell中得到的错误信息中,唯一有用的就是:它告诉了你哪个字符匹配错误。所以,我会使用grep来尝试提取那些至少有一个引号的行,然后去掉有2个引号的行:
1 grep '"' hilow.sh | egrep -v '.*".*".*' 2 echo "... smaller!
就是它:下引号漏掉了。很容易修复,然后我们接着来:
1 ./hilow.sh 2 ./hilow.sh: line 7: unexpected EOF while looking for matching `)' 3 ./hilow.sh: line 23: syntax error: unexpected end of file
又一个新问题。由于脚本中的括号表达式很少,所以很容易,肉眼雷达就能发现生成随机数时的少了半个括号。
1 number=$(($$ % $biggest) # random number, between 1 and $biggest
修正后,再接着运行:
1 ./hilow.sh 2 Guess? 33 3 ... bigger! 4 Guess? 66 5 ... bigger! 6 Guess? 99 7 ... bigger! 8 Guess? 100 9 ... bigger! 10 Guess? Ctrl+C
因为100是最大的可能生成的数字了,看来代码中有逻辑错误。这些错误难以察觉,因为没有合适的grep和sed调用来确认错误了。检查代码看看你能不能发现哪儿错了。为了调试这个错误,我准备增加一点echo语句,来输出选定的数字,以及确定我输入的就是正在测试的。有关联的代码是:
1 echo -n "Guess? " ; read answer 2 if [ "$guess" -lt $number ] ; then
事实上,当我修改了echo语句,看到了这2行,我意识到了错误:读入的变量是answer,而测试的变量是guess。一个很傻的错误,但不是一个罕见的错误(特别是如果你有拼写起来很奇怪的变量名的话)。修复这个错误,将answer改为guess即可。
运行结果:
1 ./hilow.sh 2 Guess? 50 3 ... bigger! 4 Guess? 65 5 ... smaller! 6 Guess? 55 7 ... smaller! 8 Guess? 52 9 ... bigger! 10 Guess? 53 11 Right!! Guessed 53 in 5 guesses.
分析脚本:
这个脚本中最严重的潜在bug是没有检查输入。输入除了数字以外的任何东西,脚本都会失败。把第5个脚本--合法化整型输入validint.sh调用下,是最好的。
附上最终正确的脚本:
1 #!/bin/sh 2 # hilow -- A simple number-guessing game 3 4 biggest=100 # maximum number possible 5 guess=0 # guessed by player 6 guesses=0 # number of guesses made 7 number=$(($$ % $biggest)) # random number, between 1 and $biggest 8 9 while [ $guess -ne $number ] ; do 10 echo -n "Guess? " ; read guess 11 if [ "$guess" -lt $number ] ; then 12 echo "... bigger!" 13 elif [ "$guess" -gt $number ] ; then 14 echo "... smaller!" 15 fi 16 guesses=$(($guesses + 1)) 17 done 18 19 echo "Right!! Guessed $number in $guesses guesses." 20 21 exit 0