32. 调试

调试代码要比写代码困难两倍。因此,你写代码时越多的使用奇技淫巧(自做聪明),顾名思义,你越难以调试它。 --Brian Kernighan

Bash shell中不包含内置的debug工具,甚至没有调试专用的命令和结构。当调试非功能脚本,产生语法错误或者有错别字时,往往是无用的错误提示消息。

例子 32-1. 一个错误脚本

#!/bin/bash
# ex74.sh

# 这是一个错误脚本,但是它错在哪?

a=37

if [$a -gt 27 ]
then
  echo $a
fi  

exit $?   # 0! 为什么?

脚本的输出:

./ex74.sh: [37: command not found

上边的脚本究竟哪错了(提示: 注意if的后边)

例子 32-2. 缺少关键字

脚本的输出:

注意, 其实不必参考错误信息中指出的错误行号. 这行只不过是Bash解释器最终认定错误的地方. 出错信息在报告产生语法错误的行号时, 可能会忽略脚本的注释行. 如果脚本可以执行, 但并不如你所期望的那样工作, 怎么办? 通常情况下, 这都是由常见的逻辑错误所 产生的.

例子 32-3.

可以通过把echo "$badname"行的注释符去掉,找出例子 29-3中的错误, 看一下echo出来的信息,是否按你期望的方式运行.

在这种特殊的情况下,rm "$badname"不能得到预期的结果,因为$badname不应该加双引号。加上双引号会让rm只有一个参数(这就只能匹配一个文件名).一种不完善的解决办法是去掉$badname外 面的引号, 并且重新设置$IFS, 让$IFS只包含一个换行符, IFS=$'\n'. 但是, 下面这个方法更简单.

总结一下这个问题脚本的症状:

  1. 由于"syntax error"(语法错误)使得脚本停止运行,

  2. 或者脚本能够运行, 但是并不是按照我们所期望的那样运行(逻辑错误).

  3. 脚本能够按照我们所期望的那样运行, 但是有烦人的副作用(逻辑炸弹).

如果想调试脚本, 可以用以下方式:

  1. echo语句可以放在脚本中存在疑问的位置上, 观察变量的值, 来了解脚本运行时的情况.

  2. 使用过滤器tee来检查临界点上的进程或数据流.

  3. 设置选项-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插入到脚本中, 并运行它, 就会在每个试图使用未声明变量的地方给出一个unbound variable错误信息.

  4. 使用“断言”功能在脚本的关键点进行测试的变量或条件。 (这是从C借来的一个想法)

    Example 32-4. Testing a condition with an assert

  5. 使用变量$LINENO和内建命令caller.

  6. 捕获exit返回值.

    The exit command in a script triggers a signal 0, terminating the process, that is, the script itself. [1] It is often useful to trap the exit, forcing a "printout" of variables, for example. The trap must be the first command in the script.

捕获信号

trap Specifies an action on receipt of a signal; also useful for debugging.

A signal is a message sent to a process, either by the kernel or another process, telling it to take some specified action (usually to terminate). For example, hitting a Control-C sends a user interrupt, an INT signal, to a running program.

A simple instance:

Example 32-5. Trapping at exit

Example 32-6. Cleaning up after Control-C

Note The DEBUG argument to trap causes a specified action to execute after every command in a script. This permits tracing variables, for example.

Example 32-8. Tracing a variable

Output of script:

VARIABLE-TRACE> $variable = "" VARIABLE-TRACE> $variable = "29" Just initialized $variable to 29. VARIABLE-TRACE> $variable = "29" VARIABLE-TRACE> $variable = "87" Just multiplied $variable by 3. VARIABLE-TRACE> $variable = "87" Of course, the trap command has other uses aside from debugging, such as disabling certain keystrokes within a script (see Example A-43).

Example 32-9. Running multiple processes (on an SMP box)

Note trap '' SIGNAL (two adjacent apostrophes) disables SIGNAL for the remainder of the script. trap SIGNAL restores the functioning of SIGNAL once more. This is useful to protect a critical portion of a script from an undesirable interrupt.

最后更新于

这有帮助吗?